Skip to content
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
31 changes: 29 additions & 2 deletions packages/web-runtime/src/pages/accessDenied.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
appearance="filled"
variation="primary"
v-bind="logoutButtonsAttrs"
@click="lowAssuranceLevelError && handleLogout()"
>
{{ navigateToLoginText }}
</oc-button>
Expand All @@ -41,7 +42,9 @@ import {
queryItemAsString,
useConfigStore,
useRouteQuery,
useThemeStore
useRouter,
useThemeStore,
useAuthService
} from '@ownclouders/web-pkg'

export default defineComponent({
Expand All @@ -50,7 +53,13 @@ export default defineComponent({
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)
const configStore = useConfigStore()
const authService = useAuthService()
const router = useRouter()
const redirectUrlQuery = useRouteQuery('redirectUrl')
const reasonQuery = useRouteQuery('reason')
const lowAssuranceLevelError = computed(
() => queryItemAsString(unref(reasonQuery)) === 'lowAssuranceLevel'
)

const { $gettext } = useGettext()

Expand All @@ -59,17 +68,33 @@ export default defineComponent({
const logoImg = computed(() => currentTheme.value.logo.login)

const cardTitle = computed(() => {
if (unref(lowAssuranceLevelError)) {
return $gettext('Cannot log in')
}
return $gettext('Not logged in')
})
const cardHint = computed(() => {
if (unref(lowAssuranceLevelError)) {
return $gettext(
'Please login to your CERN account using the CERN credentials instead of a guest account.'
)
}
return $gettext(
'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.'
)
})
const navigateToLoginText = computed(() => {
return $gettext('Log in again')
})
const handleLogout = async () => {
await authService.logoutUser()
await router.push({ name: 'login' })
}

const logoutButtonsAttrs = computed(() => {
if (unref(lowAssuranceLevelError)) {
return { type: 'button' }
}
const redirectUrl = queryItemAsString(unref(redirectUrlQuery))
if (configStore.options.loginUrl) {
const configLoginURL = new URL(encodeURI(configStore.options.loginUrl))
Expand Down Expand Up @@ -99,7 +124,9 @@ export default defineComponent({
footerSlogan,
navigateToLoginText,
accessDeniedHelpUrl,
logoutButtonsAttrs
logoutButtonsAttrs,
handleLogout,
lowAssuranceLevelError
}
}
})
Expand Down
3 changes: 3 additions & 0 deletions packages/web-runtime/src/router/setupAuthGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export const setupAuthGuard = (router: Router) => {

if (isUserContextRequired(router, to)) {
if (!authStore.userContextReady) {
if (authService.lowAssuranceError) {
return { name: 'accessDenied', query: { reason: 'lowAssuranceLevel' } }
}
if (unref(isDelegatingAuthentication)) {
return { path: '/web-oidc-callback' }
}
Expand Down
19 changes: 19 additions & 0 deletions packages/web-runtime/src/services/auth/authService.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ErrorResponse } from 'oidc-client-ts'
import { UserManager } from './userManager'
import { PublicLinkManager } from './publicLinkManager'
import {
Expand Down Expand Up @@ -44,6 +45,7 @@ export class AuthService implements AuthServiceInterface {
private accessTokenExpiryThreshold = 10

public hasAuthErrorOccurred: boolean
public lowAssuranceError: boolean

public initialize(
configStore: ConfigStore,
Expand All @@ -60,6 +62,7 @@ export class AuthService implements AuthServiceInterface {
this.clientService = clientService
this.router = router
this.hasAuthErrorOccurred = false
this.lowAssuranceError = false
this.ability = ability
this.language = language
this.userStore = userStore
Expand Down Expand Up @@ -165,6 +168,10 @@ export class AuthService implements AuthServiceInterface {
try {
await this.userManager.updateContext(user.access_token, fetchUserData)
} catch (e) {
if (this.isLowAssuranceLevelError(e)) {
this.lowAssuranceError = true
return
}
console.error(e)
await this.handleAuthError(unref(this.router.currentRoute))
}
Expand Down Expand Up @@ -227,6 +234,10 @@ export class AuthService implements AuthServiceInterface {
this.tokenTimerInitialized = true
}
} catch (e) {
if (this.isLowAssuranceLevelError(e)) {
this.lowAssuranceError = true
return
}
console.error(e)
await this.handleAuthError(unref(this.router.currentRoute))
}
Expand Down Expand Up @@ -269,6 +280,10 @@ export class AuthService implements AuthServiceInterface {
...(redirectRoute.query && { query: redirectRoute.query })
})
} catch (e) {
if (this.isLowAssuranceLevelError(e)) {
this.lowAssuranceError = true
return this.router.push({ name: 'accessDenied', query: { reason: 'lowAssuranceLevel' } })
}
console.warn('error during authentication:', e)
return this.handleAuthError(unref(this.router.currentRoute))
}
Expand Down Expand Up @@ -329,6 +344,10 @@ export class AuthService implements AuthServiceInterface {
this.hasAuthErrorOccurred = true
}

private isLowAssuranceLevelError(e: unknown): boolean {
return e instanceof ErrorResponse && e.error === 'low_assurance_level'
}

public async resolvePublicLink(
token: string,
passwordRequired: boolean,
Expand Down
14 changes: 13 additions & 1 deletion packages/web-runtime/src/services/auth/userManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,15 @@ export class UserManager extends OidcUserManager {

private async fetchUserInfo() {
const graphClient = this.clientService.graphAuthenticated
const [graphUser, roles] = await Promise.all([graphClient.users.getMe(), this.fetchRoles()])
let graphUser: Awaited<ReturnType<typeof graphClient.users.getMe>>, roles: SettingsBundle[]
try {
;[graphUser, roles] = await Promise.all([graphClient.users.getMe(), this.fetchRoles()])
} catch (e) {
if (e?.response?.status === 409) {
throw new ErrorResponse({ error: 'low_assurance_level' } as any)
}
throw e
}
const role = await this.fetchRole({ graphUser, roles })

this.userStore.setUser({
Expand Down Expand Up @@ -307,6 +315,10 @@ export class UserManager extends OidcUserManager {
user.access_token = revaToken
user.expires_at = claims.exp
} catch (e) {
// We do not want to fail/raise exception here, even on 409.
// If we get a 409 we still want the user to be persisted, so that
// we can terminate the session in the SSO as well (on logout).
// The 409 will be catched later.
console.error('Failed to get reva token, continue with sso one', e)
}
// end
Expand Down