Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

load login url from config #9710

Merged
merged 3 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
})
}
}