From 6cb6de6c23935c4076627f6d6710c7a5258725a5 Mon Sep 17 00:00:00 2001 From: alep85 Date: Wed, 31 Jul 2024 10:49:45 +0200 Subject: [PATCH 1/5] AAE-24139 Bump angular-oauth2-oidc version to 15 --- package-lock.json | 18 ++++++------------ package.json | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index b806377164..4c709e9d14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@ngx-translate/core": "^14.0.0", "@storybook/core-server": "8.2.6", "@storybook/theming": "8.2.6", - "angular-oauth2-oidc": "^13.0.1", + "angular-oauth2-oidc": "^15.0.1", "angular-oauth2-oidc-jwks": "^17.0.2", "apollo-angular": "^5.0.2", "chart.js": "^4.3.0", @@ -13900,16 +13900,15 @@ } }, "node_modules/angular-oauth2-oidc": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-13.0.1.tgz", - "integrity": "sha512-aL1VIv9Jqoqq31lbpUXIeNpM3GeN/ldb3KOlq0cV92amGpZs9J4YA+2rlJ5V9zb6NFNbvd7XfTntMbnNuS0+CQ==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-15.0.1.tgz", + "integrity": "sha512-5gpqO9QL+qFqMItYFHe8F6H5nOIEaowcNUc9iTDs3P1bfVYnoKoVAaijob53PuPTF4YwzdfwKWZi4Mq6P7GENQ==", "dependencies": { - "fast-sha256": "^1.3.0", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/common": ">=12.0.0", - "@angular/core": ">=12.0.0" + "@angular/common": ">=14.0.0", + "@angular/core": ">=14.0.0" } }, "node_modules/angular-oauth2-oidc-jwks": { @@ -19157,11 +19156,6 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, - "node_modules/fast-sha256": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", - "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==" - }, "node_modules/fast-uri": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", diff --git a/package.json b/package.json index a91e5b658b..56b765a0bb 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@ngx-translate/core": "^14.0.0", "@storybook/core-server": "8.2.6", "@storybook/theming": "8.2.6", - "angular-oauth2-oidc": "^13.0.1", + "angular-oauth2-oidc": "^15.0.1", "angular-oauth2-oidc-jwks": "^17.0.2", "apollo-angular": "^5.0.2", "chart.js": "^4.3.0", From 83b02ac0427ba88c8717c8de8e120fb8ecd1edeb Mon Sep 17 00:00:00 2001 From: alep85 Date: Mon, 5 Aug 2024 12:46:01 +0200 Subject: [PATCH 2/5] AAE-24139 Allow to set sessionCheckEnabled and clockSkewInSec properties --- .../src/lib/auth/models/oauth-config.model.ts | 2 + .../lib/auth/oidc/auth-config.service.spec.ts | 59 +++++++++++++++++++ .../src/lib/auth/oidc/auth-config.service.ts | 15 ++++- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/core/src/lib/auth/models/oauth-config.model.ts b/lib/core/src/lib/auth/models/oauth-config.model.ts index fad455795c..731e052440 100644 --- a/lib/core/src/lib/auth/models/oauth-config.model.ts +++ b/lib/core/src/lib/auth/models/oauth-config.model.ts @@ -31,4 +31,6 @@ export interface OauthConfigModel { redirectSilentIframeUri?: string; refreshTokenTimeout?: number; publicUrls: string[]; + clockSkewInSec?: number; + sessionChecksEnabled?: boolean; } diff --git a/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts b/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts index fc80d4948f..d35ec13f0b 100644 --- a/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts +++ b/lib/core/src/lib/auth/oidc/auth-config.service.spec.ts @@ -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({}); + }); + }); }); diff --git a/lib/core/src/lib/auth/oidc/auth-config.service.ts b/lib/core/src/lib/auth/oidc/auth-config.service.ts index 37fc9ae7cf..c79b5d0a51 100644 --- a/lib/core/src/lib/auth/oidc/auth-config.service.ts +++ b/lib/core/src/lib/auth/oidc/auth-config.service.ts @@ -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`; From 84e84d3b273ce64d9d3c7b6264b222e067666529 Mon Sep 17 00:00:00 2001 From: alep85 Date: Mon, 5 Aug 2024 19:00:32 +0200 Subject: [PATCH 3/5] AAE-24139 Update angular-oauth2-oidc version to 15 in the core deps --- lib/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/package.json b/lib/core/package.json index 6f5980d914..534d5c3ea7 100644 --- a/lib/core/package.json +++ b/lib/core/package.json @@ -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": { From e2cd4d4b84a162cdd57ba3fd3562c6ef058438fa Mon Sep 17 00:00:00 2001 From: alep85 Date: Tue, 3 Sep 2024 09:26:19 +0200 Subject: [PATCH 4/5] AAE-24139 Remove authentication tokens when the token is no longer valid and reload the page to let oauth library refresh the token --- .../auth/oidc/redirect-auth.service.spec.ts | 23 +++++++++++++++ .../lib/auth/oidc/redirect-auth.service.ts | 29 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts b/lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts index 6134b27693..9928519bfc 100644 --- a/lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts +++ b/lib/core/src/lib/auth/oidc/redirect-auth.service.spec.ts @@ -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).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(); + }); + }); diff --git a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts index edfc2d141a..7bbeb9b961 100644 --- a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts +++ b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts @@ -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; + 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(); + } + } From df6ded52bbdf0315b7660bb43caa6e8f67f75c46 Mon Sep 17 00:00:00 2001 From: alep85 Date: Mon, 9 Sep 2024 17:06:49 +0200 Subject: [PATCH 5/5] fix lint issue --- lib/core/src/lib/auth/oidc/redirect-auth.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts index 7bbeb9b961..16aac17384 100644 --- a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts +++ b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts @@ -86,7 +86,7 @@ export class RedirectAuthService extends AuthService { 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.AUTH_STORAGE_ITEMS.map((item: string) => this._oauthStorage.removeItem(item)); this.reloadPage(); } });