Skip to content

Commit

Permalink
load login url from config (#9710)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexAndBear authored Sep 26, 2023
1 parent 53fde0c commit ba6247c
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 13 deletions.
7 changes: 7 additions & 0 deletions changelog/unreleased/enhancement-make-login-url-configurable
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Make login url configurable

We've added a new configuration option loginUrl to web, this is helpful if you use an external IdP and the login
is out of web/OCIS context.

https://github.com/owncloud/ocis/pull/7317
https://github.com/owncloud/web/issues/9707
4 changes: 4 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ Depending on the backend you are using, there are sample config files provided i
- `options.contextHelpersReadMore` Specifies whether the "Read more" link should be displayed or not.
- `options.openLinksWithDefaultApp` Specifies whether single file link shares should be opened with default app or not.
- `options.tokenStorageLocal` Specifies whether the access token will be stored in the local storage when set to `true` or in the session storage when set to `false`. If stored in the local storage, login state will be persisted across multiple browser tabs, means no additional logins are required. Defaults to `true`.
- `options.loginUrl` Specifies the target URL to the login page. This is helpful when an external IdP is used. This option is disabled by default. Example URL like: 'https://www.myidp.com/login'.
- `options.logoutUrl` Adds a link to the user's profile page to point him to an external page, where he can manage his session and devices. This is helpful when an external IdP is used. This option is disabled by default.
- `options.imprintUrl` Specifies the target URL for the imprint link valid for the ocis instance in the account menu.
- `options.privacyUrl` Specifies the target URL for the privacy link valid for the ocis instance in the account menu.

#### Scripts and Styles

Expand Down
1 change: 1 addition & 0 deletions packages/web-pkg/src/configuration/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export class ConfigurationManager {
)
set(this.optionsConfiguration, 'upload.companionUrl', get(options, 'upload.companionUrl', ''))
set(this.optionsConfiguration, 'tokenStorageLocal', get(options, 'tokenStorageLocal', true))
set(this.optionsConfiguration, 'loginUrl', get(options, 'loginUrl', ''))
}

get options(): OptionsConfiguration {
Expand Down
1 change: 1 addition & 0 deletions packages/web-pkg/src/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface OptionsConfiguration {
routing?: RoutingOptionsConfiguration
upload?: UploadOptionsConfiguration
logoutUrl?: string
loginUrl?: string
contextHelpersReadMore?: boolean
contextHelpers?: boolean
openAppsInTab?: boolean
Expand Down
41 changes: 28 additions & 13 deletions packages/web-runtime/src/pages/accessDenied.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,38 @@
</div>
<oc-button
id="exitAnchor"
type="router-link"
class="oc-mt-m oc-width-medium"
:to="logoutLink"
size="large"
appearance="filled"
variation="primary"
v-bind="logoutButtonsAttrs"
v-text="navigateToLoginText"
/>
</div>
</template>

<script lang="ts">
import { computed, defineComponent, unref } from 'vue'
import { useRoute, useStore } from 'web-pkg'
import { queryItemAsString, useConfigurationManager, useRouteQuery, useStore } from 'web-pkg'
import { useGettext } from 'vue3-gettext'
export default defineComponent({
name: 'AccessDeniedPage',
setup() {
const store = useStore()
const route = useRoute()
const configurationManager = useConfigurationManager()
const redirectUrlQuery = useRouteQuery('redirectUrl')
const { $gettext } = useGettext()
const logoImg = computed(() => {
return store.getters.configuration.currentTheme.logo.login
return store.getters.configuration?.currentTheme?.logo?.login
})
const accessDeniedHelpUrl = computed(() => {
return (
store.getters.configuration.commonTheme.accessDeniedHelpUrl ||
store.getters.configuration.options.accessDeniedHelpUrl
store.getters.configuration?.commonTheme?.accessDeniedHelpUrl ||
store.getters.configuration?.options?.accessDeniedHelpUrl
)
})
const cardTitle = computed(() => {
Expand All @@ -64,17 +65,31 @@ export default defineComponent({
)
})
const footerSlogan = computed(() => {
return store.getters.configuration.currentTheme.general.slogan
return store.getters.configuration?.currentTheme?.general?.slogan
})
const navigateToLoginText = computed(() => {
return $gettext('Log in again')
})
const logoutLink = computed(() => {
const redirectUrl = unref(route).query?.redirectUrl
const logoutButtonsAttrs = computed(() => {
const redirectUrl = queryItemAsString(unref(redirectUrlQuery))
if (configurationManager.options.loginUrl) {
const configLoginURL = new URL(encodeURI(configurationManager.options.loginUrl))
if (redirectUrl) {
configLoginURL.searchParams.append('redirectUrl', redirectUrl)
}
return {
type: 'a',
href: configLoginURL.toString()
}
}
return {
name: 'login',
query: {
...(redirectUrl && { redirectUrl })
type: 'router-link',
to: {
name: 'login',
query: {
...(redirectUrl && { redirectUrl })
}
}
}
})
Expand All @@ -86,7 +101,7 @@ export default defineComponent({
footerSlogan,
navigateToLoginText,
accessDeniedHelpUrl,
logoutLink
logoutButtonsAttrs
}
}
})
Expand Down
1 change: 1 addition & 0 deletions packages/web-runtime/src/store/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const state = {
contextHelpersReadMore: true,
openLinksWithDefaultApp: true,
tokenStorageLocal: true,
loginUrl: '',
privacyUrl: '',
imprintUrl: '',
accessDeniedHelpUrl: ''
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`access denied page renders component 1`] = `
<div class="oc-height-viewport oc-flex oc-flex-column oc-flex-center oc-flex-middle">
<div class="oc-login-card">
<img alt="" aria-hidden="true" class="oc-login-logo">
<div class="oc-login-card-body oc-width-medium">
<h2 class="oc-login-card-title">Not logged in</h2>
<p>This could be because of a routine safety log out, or because your account is either inactive or not yet authorized for use. Please try logging in after a while or seek help from your Administrator.</p>
<!--v-if-->
</div>
<div class="oc-login-card-footer oc-pt-rm">
<p></p>
</div>
</div>
<a attrs="[object Object]" class="oc-button oc-rounded oc-button-l oc-button-justify-content-center oc-button-gap-m oc-button-primary oc-button-primary-filled oc-mt-m oc-width-medium" id="exitAnchor" name="login">Log in again</a>
</div>
`;
66 changes: 66 additions & 0 deletions packages/web-runtime/tests/unit/pages/accessDenied.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import accessDenied from '../../../src/pages/accessDenied.vue'
import {
createStore,
defaultComponentMocks,
defaultPlugins,
mount,
defaultStoreMockOptions
} from 'web-test-helpers'
import { mock } from 'jest-mock-extended'

const selectors = {
logInAgainButton: '#exitAnchor'
}

import { ConfigurationManager, useConfigurationManager } from 'web-pkg/src'

jest.mock('web-pkg/src/composables/configuration/useConfigurationManager')

describe('access denied page', () => {
it('renders component', () => {
const { wrapper } = getWrapper()
expect(wrapper.html()).toMatchSnapshot()
})
describe('"Log in again" button', () => {
it('navigates to "loginUrl" if set in config', () => {
const loginUrl = 'https://myidp.int/login'
const { wrapper } = getWrapper({ loginUrl })

const logInAgainButton = wrapper.find(selectors.logInAgainButton)
const loginAgainUrl = new URL(logInAgainButton.attributes().href)
loginAgainUrl.search = ''

expect(logInAgainButton.exists()).toBeTruthy()
expect(loginAgainUrl.toString()).toEqual(loginUrl)
})
})
})

function getWrapper({ loginUrl = '' } = {}) {
const mocks = {
...defaultComponentMocks()
}
const storeOptions = { ...defaultStoreMockOptions }

jest.mocked(useConfigurationManager).mockImplementation(() =>
mock<ConfigurationManager>({
options: {
loginUrl
}
} as any)
)

const store = createStore(storeOptions)

return {
storeOptions,
mocks,
wrapper: mount(accessDenied, {
global: {
plugins: [...defaultPlugins(), store],
mocks,
provide: mocks
}
})
}
}

0 comments on commit ba6247c

Please sign in to comment.