mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
AAE-24139 Fix unauthorized user error when access token is not valid (#10012)
* AAE-24139 Bump angular-oauth2-oidc version to 15 * AAE-24139 Allow to set sessionCheckEnabled and clockSkewInSec properties * AAE-24139 Update angular-oauth2-oidc version to 15 in the core deps * AAE-24139 Remove authentication tokens when the token is no longer valid and reload the page to let oauth library refresh the token * fix lint issue
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cropperjs": "^1.5.13",
|
||||
"angular-oauth2-oidc": "^13.0.1",
|
||||
"angular-oauth2-oidc": "^15.0.1",
|
||||
"angular-oauth2-oidc-jwks": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@@ -31,4 +31,6 @@ export interface OauthConfigModel {
|
||||
redirectSilentIframeUri?: string;
|
||||
refreshTokenTimeout?: number;
|
||||
publicUrls: string[];
|
||||
clockSkewInSec?: number;
|
||||
sessionChecksEnabled?: boolean;
|
||||
}
|
||||
|
@@ -221,4 +221,63 @@ describe('AuthConfigService', () => {
|
||||
expect(service.loadAppConfig().postLogoutRedirectUri).toBe('http://localhost:3000/asd');
|
||||
});
|
||||
});
|
||||
|
||||
describe('clockSkewInSec', () => {
|
||||
it('should return clockSkewInSec equal to 0', () => {
|
||||
const expectedClockSkewInSec = 0;
|
||||
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ clockSkewInSec: 0 } as any);
|
||||
expect(service.loadAppConfig().clockSkewInSec).toBe(expectedClockSkewInSec);
|
||||
});
|
||||
it('should not return clockSkewInSec if is not defined', () => {
|
||||
spyOnProperty(appConfigService, 'oauth2').and.returnValue({} as any);
|
||||
expect(service.loadAppConfig().clockSkewInSec).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not return clockSkewInSec if is undefined', () => {
|
||||
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ clockSkewInSec: undefined } as any);
|
||||
expect(service.loadAppConfig().clockSkewInSec).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return empty object if clockSkewInSec is null', () => {
|
||||
const mockOauth2Value = { clockSkewInSec: null } as any;
|
||||
expect(service.getClockSkewInSec(mockOauth2Value)).toEqual({});
|
||||
});
|
||||
|
||||
it('should return empty object if clockSkewInSec is a string', () => {
|
||||
const mockOauth2Value = { clockSkewInSec: 'null' } as any;
|
||||
expect(service.getClockSkewInSec(mockOauth2Value)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sessionChecksEnabled', () => {
|
||||
it('should return sessionChecksEnabled equal to true', () => {
|
||||
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ sessionChecksEnabled: true } as any);
|
||||
expect(service.loadAppConfig().sessionChecksEnabled).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return sessionChecksEnabled equal to false', () => {
|
||||
spyOnProperty(appConfigService, 'oauth2').and.returnValue({ sessionChecksEnabled: false } as any);
|
||||
expect(service.loadAppConfig().sessionChecksEnabled).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not return sessionChecksEnabled if is not defined', () => {
|
||||
expect(service.getSessionCheckEnabled({} as any)).toEqual({});
|
||||
});
|
||||
|
||||
it('should not return sessionChecksEnabled if is a string', () => {
|
||||
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: 'fake' } as any)).toEqual({});
|
||||
});
|
||||
|
||||
it('should not return sessionChecksEnabled if is undefined', () => {
|
||||
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: undefined } as any)).toEqual({});
|
||||
});
|
||||
|
||||
it('should not return sessionChecksEnabled if is null', () => {
|
||||
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: null } as any)).toEqual({});
|
||||
});
|
||||
|
||||
it('should not return sessionChecksEnabled if is a number', () => {
|
||||
expect(service.getSessionCheckEnabled({ sessionChecksEnabled: 666 } as any)).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -20,6 +20,7 @@ import { AuthConfig } from 'angular-oauth2-oidc';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { AUTH_MODULE_CONFIG, AuthModuleConfig } from './auth-config';
|
||||
import { OauthConfigModel } from '../models/oauth-config.model';
|
||||
|
||||
/**
|
||||
* Create auth configuration factory
|
||||
@@ -51,6 +52,8 @@ export class AuthConfigService {
|
||||
const origin = this.getLocationOrigin();
|
||||
const redirectUri = this.getRedirectUri();
|
||||
const customQueryParams = oauth2.audience ? { audience: oauth2.audience } : {};
|
||||
const clockSkewInSec = this.getClockSkewInSec(oauth2);
|
||||
const sessionChecksEnabled = this.getSessionCheckEnabled(oauth2);
|
||||
|
||||
return new AuthConfig({
|
||||
...oauth2,
|
||||
@@ -65,10 +68,20 @@ export class AuthConfigService {
|
||||
dummyClientSecret: oauth2.secret || '',
|
||||
logoutUrl: oauth2.logoutUrl,
|
||||
customQueryParams,
|
||||
...(oauth2.codeFlow && { responseType: 'code' })
|
||||
...(oauth2.codeFlow && { responseType: 'code' }),
|
||||
...clockSkewInSec,
|
||||
...sessionChecksEnabled
|
||||
});
|
||||
}
|
||||
|
||||
getSessionCheckEnabled(oauth2: OauthConfigModel) {
|
||||
return typeof oauth2.sessionChecksEnabled === 'boolean' ? { sessionChecksEnabled: oauth2.sessionChecksEnabled } : {};
|
||||
}
|
||||
|
||||
getClockSkewInSec(oauth2: OauthConfigModel) {
|
||||
return typeof oauth2.clockSkewInSec === 'number' ? { clockSkewInSec: oauth2.clockSkewInSec } : {};
|
||||
}
|
||||
|
||||
getRedirectUri(): string {
|
||||
// required for this package as we handle the returned token on this view, with is provided by the AuthModule
|
||||
const viewUrl = `view/authentication-confirmation`;
|
||||
|
@@ -34,6 +34,7 @@ describe('RedirectAuthService', () => {
|
||||
events: oauthEvents$,
|
||||
configure: () => {},
|
||||
hasValidAccessToken: jasmine.createSpy().and.returnValue(true),
|
||||
hasValidIdToken: jasmine.createSpy().and.returnValue(true),
|
||||
setupAutomaticSilentRefresh: () => {
|
||||
mockOauthService.silentRefresh();
|
||||
mockOauthService.refreshToken();
|
||||
@@ -53,6 +54,7 @@ describe('RedirectAuthService', () => {
|
||||
|
||||
TestBed.inject(OAuthService);
|
||||
service = TestBed.inject(RedirectAuthService);
|
||||
spyOn(service, 'reloadPage').and.callFake(() => {});
|
||||
spyOn(service, 'ensureDiscoveryDocument').and.resolveTo(true);
|
||||
mockOauthService.getAccessToken = () => 'access-token';
|
||||
});
|
||||
@@ -93,4 +95,25 @@ describe('RedirectAuthService', () => {
|
||||
expect(refreshTokenCalled).toBe(true);
|
||||
expect(silentRefreshCalled).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove all auth items from the storage if access token is set and is not authenticated', () => {
|
||||
mockOauthService.getAccessToken = () => 'access-token';
|
||||
spyOnProperty(service, 'authenticated', 'get').and.returnValue(false);
|
||||
(mockOauthService.events as Subject<OAuthEvent>).next({ type: 'discovery_document_loaded' } as OAuthEvent);
|
||||
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('access_token');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('access_token_stored_at');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('expires_at');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('granted_scopes');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token_claims_obj');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token_expires_at');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('id_token_stored_at');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('nonce');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('PKCE_verifier');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('refresh_token');
|
||||
expect(mockOAuthStorage.removeItem).toHaveBeenCalledWith('session_state');
|
||||
expect(service.reloadPage).toHaveBeenCalledOnceWith();
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -19,7 +19,7 @@ import { Inject, Injectable, inject } from '@angular/core';
|
||||
import { AuthConfig, AUTH_CONFIG, OAuthErrorEvent, OAuthEvent, OAuthService, OAuthStorage, TokenResponse, LoginOptions, OAuthSuccessEvent } from 'angular-oauth2-oidc';
|
||||
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, filter, map, shareReplay, take } from 'rxjs/operators';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AUTH_MODULE_CONFIG, AuthModuleConfig } from './auth-config';
|
||||
|
||||
@@ -53,6 +53,21 @@ export class RedirectAuthService extends AuthService {
|
||||
|
||||
private authConfig!: AuthConfig | Promise<AuthConfig>;
|
||||
|
||||
private readonly AUTH_STORAGE_ITEMS: string[] = [
|
||||
'access_token',
|
||||
'access_token_stored_at',
|
||||
'expires_at',
|
||||
'granted_scopes',
|
||||
'id_token',
|
||||
'id_token_claims_obj',
|
||||
'id_token_expires_at',
|
||||
'id_token_stored_at',
|
||||
'nonce',
|
||||
'PKCE_verifier',
|
||||
'refresh_token',
|
||||
'session_state'
|
||||
];
|
||||
|
||||
constructor(
|
||||
private oauthService: OAuthService,
|
||||
private _oauthStorage: OAuthStorage,
|
||||
@@ -69,6 +84,13 @@ export class RedirectAuthService extends AuthService {
|
||||
shareReplay(1)
|
||||
);
|
||||
|
||||
this.oauthService.events.pipe(take(1)).subscribe(() => {
|
||||
if(this.oauthService.getAccessToken() && !this.authenticated){
|
||||
this.AUTH_STORAGE_ITEMS.map((item: string) => this._oauthStorage.removeItem(item));
|
||||
this.reloadPage();
|
||||
}
|
||||
});
|
||||
|
||||
this.onLogin = this.authenticated$.pipe(
|
||||
filter((authenticated) => authenticated),
|
||||
map(() => undefined)
|
||||
@@ -223,4 +245,9 @@ export class RedirectAuthService extends AuthService {
|
||||
updateIDPConfiguration(config: AuthConfig) {
|
||||
this.oauthService.configure(config);
|
||||
}
|
||||
|
||||
reloadPage() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user