From 885c841906c2aa3831c3d01a37cc2b348e56535e Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Tue, 15 Nov 2022 16:23:16 +0100 Subject: [PATCH 1/9] Updated POST --- .eslintrc.json | 1 + .../src/lib/auth-options.ts | 7 ++ .../src/lib/flows/reset-auth-data.service.ts | 6 +- .../logoff-revocation.service.ts | 81 +++++++++++----- .../src/lib/oidc.security.service.ts | 20 ++-- .../src/lib/utils/url/url.service.ts | 97 +++++++++++-------- .../validation/state-validation.service.ts | 4 + 7 files changed, 141 insertions(+), 75 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index caadfa8af..9f5ad98e5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -31,6 +31,7 @@ "no-case-declarations": ["error"], "no-empty": ["error"], "@typescript-eslint/no-empty-function": ["error"], + "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": ["error"], "@typescript-eslint/ban-types": ["error"], "no-useless-escape": ["error"], diff --git a/projects/angular-auth-oidc-client/src/lib/auth-options.ts b/projects/angular-auth-oidc-client/src/lib/auth-options.ts index 8d90b23c8..b6969f86c 100644 --- a/projects/angular-auth-oidc-client/src/lib/auth-options.ts +++ b/projects/angular-auth-oidc-client/src/lib/auth-options.ts @@ -4,3 +4,10 @@ export interface AuthOptions { /** overrides redirectUrl from configuration */ redirectUrl?: string; } + +export interface LogoutAuthOptions { + customParams?: { [key: string]: string | number | boolean }; + urlHandler?(url: string): any; + /** overrides redirectUrl from configuration */ + logoffMethod?: 'GET' | 'POST'; +} diff --git a/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.ts b/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.ts index 0b810d45d..7b5f1caee 100644 --- a/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { AuthStateService } from '../auth-state/auth-state.service'; import { OpenIdConfiguration } from '../config/openid-configuration'; +import { LoggerService } from '../logging/logger.service'; import { UserService } from '../user-data/user.service'; import { FlowsDataService } from './flows-data.service'; @@ -9,12 +10,15 @@ export class ResetAuthDataService { constructor( private readonly authStateService: AuthStateService, private readonly flowsDataService: FlowsDataService, - private readonly userService: UserService + private readonly userService: UserService, + private readonly loggerService: LoggerService ) {} resetAuthorizationData(currentConfiguration: OpenIdConfiguration, allConfigs: OpenIdConfiguration[]): void { this.userService.resetUserDataInStore(currentConfiguration, allConfigs); this.flowsDataService.resetStorageFlowData(currentConfiguration); this.authStateService.setUnauthenticatedAndFireEvent(currentConfiguration, allConfigs); + + this.loggerService.logDebug(currentConfiguration, 'Local Login information cleaned up and event fired'); } } diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts index 08d9bf93c..5975e9ce6 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; import { Observable, of, throwError } from 'rxjs'; import { catchError, retry, switchMap, tap } from 'rxjs/operators'; import { DataService } from '../api/data.service'; -import { AuthOptions } from '../auth-options'; +import { LogoutAuthOptions } from '../auth-options'; import { OpenIdConfiguration } from '../config/openid-configuration'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { CheckSessionService } from '../iframe/check-session.service'; @@ -26,28 +26,34 @@ export class LogoffRevocationService { // Logs out on the server and the local client. // If the server state has changed, check session, then only a local logout. - logoff(config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[], authOptions?: AuthOptions): void { - const { urlHandler, customParams } = authOptions || {}; + logoff(config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[], logoutAuthOptions?: LogoutAuthOptions): Observable { + this.loggerService.logDebug(config, 'logoff, remove auth'); - this.loggerService.logDebug(config, 'logoff, remove auth '); - - const endSessionUrl = this.getEndSessionUrl(config, customParams); + const { urlHandler, customParams } = logoutAuthOptions || {}; + const endSessionUrl = this.urlService.getEndSessionUrl(config, customParams); this.resetAuthDataService.resetAuthorizationData(config, allConfigs); if (!endSessionUrl) { - this.loggerService.logDebug(config, 'only local login cleaned up, no end_session_endpoint'); + this.loggerService.logDebug(config, 'No endsessionUrl present. Logoff was only locally. Returning.'); - return; + return of(null); } if (this.checkSessionService.serverStateChanged(config)) { - this.loggerService.logDebug(config, 'only local login cleaned up, server session has changed'); - } else if (urlHandler) { + this.loggerService.logDebug(config, 'Server State changed. Logoff was only locally. Returning.'); + + return of(null); + } + + if (urlHandler) { + this.loggerService.logDebug(config, `Custom UrlHandler found. Using this to handle logoff with url '${endSessionUrl}'`); urlHandler(endSessionUrl); - } else { - this.redirectService.redirectTo(endSessionUrl); + + return of(null); } + + return this.logout(logoutAuthOptions, endSessionUrl, config); } logoffLocal(config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[]): void { @@ -61,12 +67,16 @@ export class LogoffRevocationService { // The refresh token and and the access token are revoked on the server. If the refresh token does not exist // only the access token is revoked. Then the logout run. - logoffAndRevokeTokens(config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[], authOptions?: AuthOptions): Observable { + logoffAndRevokeTokens( + config: OpenIdConfiguration, + allConfigs: OpenIdConfiguration[], + logoutAuthOptions?: LogoutAuthOptions + ): Observable { const { revocationEndpoint } = this.storagePersistenceService.read('authWellKnownEndPoints', config) || {}; if (!revocationEndpoint) { this.loggerService.logDebug(config, 'revocation endpoint not supported'); - this.logoff(config, allConfigs, authOptions); + this.logoff(config, allConfigs, logoutAuthOptions); return of(null); } @@ -81,7 +91,7 @@ export class LogoffRevocationService { return throwError(() => new Error(errorMessage)); }), - tap(() => this.logoff(config, allConfigs, authOptions)) + tap(() => this.logoff(config, allConfigs, logoutAuthOptions)) ); } else { return this.revokeAccessToken(config).pipe( @@ -92,7 +102,7 @@ export class LogoffRevocationService { return throwError(() => new Error(errorMessage)); }), - tap(() => this.logoff(config, allConfigs, authOptions)) + tap(() => this.logoff(config, allConfigs, logoutAuthOptions)) ); } } @@ -119,21 +129,34 @@ export class LogoffRevocationService { return this.sendRevokeRequest(configuration, body); } - getEndSessionUrl(configuration: OpenIdConfiguration, customParams?: { [p: string]: string | number | boolean }): string | null { - const idToken = this.storagePersistenceService.getIdToken(configuration); - const { customParamsEndSessionRequest } = configuration; + private logout(authOptions: LogoutAuthOptions, endSessionUrl: string, config: OpenIdConfiguration): Observable { + const { logoffMethod } = authOptions; - const mergedParams = { ...customParamsEndSessionRequest, ...customParams }; + if (!logoffMethod || logoffMethod === 'GET') { + return of(this.redirectService.redirectTo(endSessionUrl)); + } - return this.urlService.createEndSessionUrl(idToken, configuration, mergedParams); + const { state, logout_hint, ui_locales } = authOptions?.customParams || {}; + const { clientId } = config; + const idToken = this.storagePersistenceService.getIdToken(config); + const postLogoutRedirectUrl = this.urlService.getPostLogoutRedirectUrl(config); + const headers = this.getHeaders(); + const { url } = this.urlService.getEndSessionEndpoint(config); + const body = { + id_token_hint: idToken, + client_id: clientId, + post_logout_redirect_uri: postLogoutRedirectUrl, + state, + logout_hint, + ui_locales, + }; + + return this.dataService.post(url, body, config, headers); } private sendRevokeRequest(configuration: OpenIdConfiguration, body: string): Observable { const url = this.urlService.getRevocationEndpointUrl(configuration); - - let headers: HttpHeaders = new HttpHeaders(); - - headers = headers.set('Content-Type', 'application/x-www-form-urlencoded'); + const headers = this.getHeaders(); return this.dataService.post(url, body, configuration, headers).pipe( retry(2), @@ -151,4 +174,12 @@ export class LogoffRevocationService { }) ); } + + private getHeaders(): HttpHeaders { + let headers: HttpHeaders = new HttpHeaders(); + + headers = headers.set('Content-Type', 'application/x-www-form-urlencoded'); + + return headers; + } } diff --git a/projects/angular-auth-oidc-client/src/lib/oidc.security.service.ts b/projects/angular-auth-oidc-client/src/lib/oidc.security.service.ts index b90ff7616..f6c55b979 100644 --- a/projects/angular-auth-oidc-client/src/lib/oidc.security.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/oidc.security.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; -import { AuthOptions } from './auth-options'; +import { concatMap, map, switchMap } from 'rxjs/operators'; +import { AuthOptions, LogoutAuthOptions } from './auth-options'; import { AuthenticatedResult } from './auth-state/auth-result'; import { AuthStateService } from './auth-state/auth-state.service'; import { CheckAuthService } from './auth-state/check-auth.service'; @@ -327,12 +327,12 @@ export class OidcSecurityService { * * @returns An observable when the action is finished */ - logoffAndRevokeTokens(configId?: string, authOptions?: AuthOptions): Observable { + logoffAndRevokeTokens(configId?: string, logoutAuthOptions?: LogoutAuthOptions): Observable { return this.configurationService .getOpenIDConfigurations(configId) .pipe( - switchMap(({ allConfigs, currentConfig }) => - this.logoffRevocationService.logoffAndRevokeTokens(currentConfig, allConfigs, authOptions) + concatMap(({ allConfigs, currentConfig }) => + this.logoffRevocationService.logoffAndRevokeTokens(currentConfig, allConfigs, logoutAuthOptions) ) ); } @@ -344,10 +344,12 @@ export class OidcSecurityService { * @param configId The configId to perform the action in behalf of. If not passed, the first configs will be taken * @param authOptions with custom parameters and/or an custom url handler */ - logoff(configId?: string, authOptions?: AuthOptions): void { - this.configurationService + logoff(configId?: string, logoutAuthOptions?: LogoutAuthOptions): Observable { + return this.configurationService .getOpenIDConfigurations(configId) - .subscribe(({ allConfigs, currentConfig }) => this.logoffRevocationService.logoff(currentConfig, allConfigs, authOptions)); + .pipe( + concatMap(({ allConfigs, currentConfig }) => this.logoffRevocationService.logoff(currentConfig, allConfigs, logoutAuthOptions)) + ); } /** @@ -415,7 +417,7 @@ export class OidcSecurityService { getEndSessionUrl(customParams?: { [p: string]: string | number | boolean }, configId?: string): Observable { return this.configurationService .getOpenIDConfiguration(configId) - .pipe(map((config) => this.logoffRevocationService.getEndSessionUrl(config, customParams))); + .pipe(map((config) => this.urlService.getEndSessionUrl(config, customParams))); } /** diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts index c13be7eea..dc56a2281 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts @@ -102,18 +102,7 @@ export class UrlService { return of(this.createUrlImplicitFlowAuthorize(config, authOptions) || ''); } - createEndSessionUrl( - idTokenHint: string, - configuration: OpenIdConfiguration, - customParamsEndSession?: { [p: string]: string | number | boolean } - ): string { - // Auth0 needs a special logout url - // See https://auth0.com/docs/api/authentication#logout - - if (this.isAuth0Endpoint(configuration)) { - return this.composeAuth0Endpoint(configuration); - } - + getEndSessionEndpoint(configuration: OpenIdConfiguration): { url: string; existingParams: string } { const authWellKnownEndPoints = this.storagePersistenceService.read('authWellKnownEndPoints', configuration); const endSessionEndpoint = authWellKnownEndPoints?.endSessionEndpoint; @@ -122,25 +111,21 @@ export class UrlService { } const urlParts = endSessionEndpoint.split('?'); - const authorizationEndSessionUrl = urlParts[0]; + const url = urlParts[0]; const existingParams = urlParts[1]; - let params = this.createHttpParams(existingParams); - if (!!idTokenHint) { - params = params.set('id_token_hint', idTokenHint); - } - - const postLogoutRedirectUri = this.getPostLogoutRedirectUrl(configuration); - - if (postLogoutRedirectUri) { - params = params.append('post_logout_redirect_uri', postLogoutRedirectUri); - } + return { + url, + existingParams, + }; + } - if (customParamsEndSession) { - params = this.appendCustomParams({ ...customParamsEndSession }, params); - } + getEndSessionUrl(configuration: OpenIdConfiguration, customParams?: { [p: string]: string | number | boolean }): string | null { + const idToken = this.storagePersistenceService.getIdToken(configuration); + const { customParamsEndSessionRequest } = configuration; + const mergedParams = { ...customParamsEndSessionRequest, ...customParams }; - return `${authorizationEndSessionUrl}?${params}`; + return this.createEndSessionUrl(idToken, configuration, mergedParams); } createRevocationEndpointBodyAccessToken(token: any, configuration: OpenIdConfiguration): string { @@ -206,7 +191,7 @@ export class UrlService { params = params.set('grant_type', 'authorization_code'); params = params.set('client_id', clientId); - if(!configuration.disablePkce) { + if (!configuration.disablePkce) { const codeVerifier = this.flowsDataService.getCodeVerifier(configuration); if (!codeVerifier) { @@ -316,6 +301,50 @@ export class UrlService { ); } + getPostLogoutRedirectUrl(configuration: OpenIdConfiguration): string { + const { postLogoutRedirectUri } = configuration; + + if (!postLogoutRedirectUri) { + this.loggerService.logError(configuration, `could not get postLogoutRedirectUri, was: `, postLogoutRedirectUri); + + return null; + } + + return postLogoutRedirectUri; + } + + private createEndSessionUrl( + idTokenHint: string, + configuration: OpenIdConfiguration, + customParamsEndSession?: { [p: string]: string | number | boolean } + ): string { + // Auth0 needs a special logout url + // See https://auth0.com/docs/api/authentication#logout + + if (this.isAuth0Endpoint(configuration)) { + return this.composeAuth0Endpoint(configuration); + } + + const { url, existingParams } = this.getEndSessionEndpoint(configuration); + let params = this.createHttpParams(existingParams); + + if (!!idTokenHint) { + params = params.set('id_token_hint', idTokenHint); + } + + const postLogoutRedirectUri = this.getPostLogoutRedirectUrl(configuration); + + if (postLogoutRedirectUri) { + params = params.append('post_logout_redirect_uri', postLogoutRedirectUri); + } + + if (customParamsEndSession) { + params = this.appendCustomParams({ ...customParamsEndSession }, params); + } + + return `${url}?${params}`; + } + private createAuthorizeUrl( codeChallenge: string, redirectUrl: string, @@ -543,18 +572,6 @@ export class UrlService { return silentRenewUrl; } - private getPostLogoutRedirectUrl(configuration: OpenIdConfiguration): string { - const { postLogoutRedirectUri } = configuration; - - if (!postLogoutRedirectUri) { - this.loggerService.logError(configuration, `could not get postLogoutRedirectUri, was: `, postLogoutRedirectUri); - - return null; - } - - return postLogoutRedirectUri; - } - private getClientId(configuration: OpenIdConfiguration): string { const { clientId } = configuration; diff --git a/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.ts b/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.ts index 886fdd243..d540a55c0 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.ts @@ -54,6 +54,8 @@ export class StateValidationService { if (disableIdTokenValidation) { toReturn.state = ValidationResult.Ok; + // TODO TESTING + toReturn.authResponseIsValid = true; return of(toReturn); } @@ -63,6 +65,8 @@ export class StateValidationService { if (isInRefreshTokenFlow && !hasIdToken) { toReturn.state = ValidationResult.Ok; + // TODO TESTING + toReturn.authResponseIsValid = true; return of(toReturn); } From e43dfe7729b59ceb8d642d44defa9789f8f59a90 Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Tue, 15 Nov 2022 16:37:56 +0100 Subject: [PATCH 2/9] Make Tests running again --- .../lib/flows/reset-auth-data.service.spec.ts | 2 + .../logoff-revocation.service.spec.ts | 38 +-- .../logoff-revocation.service.ts | 2 +- .../src/lib/oidc.security.service.spec.ts | 13 +- .../src/lib/utils/url/url.service.spec.ts | 224 +++++++++--------- .../state-validation.service.spec.ts | 6 +- 6 files changed, 144 insertions(+), 141 deletions(-) diff --git a/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.spec.ts index ef7e84dda..ebb76f896 100644 --- a/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/flows/reset-auth-data.service.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { mockClass } from '../../test/auto-mock'; import { AuthStateService } from '../auth-state/auth-state.service'; +import { LoggerService } from '../logging/logger.service'; import { UserService } from '../user-data/user.service'; import { FlowsDataService } from './flows-data.service'; import { ResetAuthDataService } from './reset-auth-data.service'; @@ -18,6 +19,7 @@ describe('ResetAuthDataService', () => { { provide: AuthStateService, useClass: mockClass(AuthStateService) }, { provide: FlowsDataService, useClass: mockClass(FlowsDataService) }, { provide: UserService, useClass: mockClass(UserService) }, + { provide: LoggerService, useClass: mockClass(LoggerService) }, ], }); }); diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts index af84b211a..ca2ce0579 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts @@ -382,26 +382,26 @@ describe('Logout and Revoke Service', () => { })); }); - describe('getEndSessionUrl', () => { - it('uses id_token parameter from persistence if no param is provided', () => { - // Arrange - const paramToken = 'damienId'; - - spyOn(storagePersistenceService, 'getIdToken').and.returnValue(paramToken); - const revocationSpy = spyOn(urlService, 'createEndSessionUrl'); - const config = { configId: 'configId1' }; - - // Act - service.getEndSessionUrl(config); - // Assert - expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config, {}); - }); - }); + // describe('getEndSessionUrl', () => { + // it('uses id_token parameter from persistence if no param is provided', () => { + // // Arrange + // const paramToken = 'damienId'; + + // spyOn(storagePersistenceService, 'getIdToken').and.returnValue(paramToken); + // const revocationSpy = spyOn(urlService, 'createEndSessionUrl'); + // const config = { configId: 'configId1' }; + + // // Act + // service.getEndSessionUrl(config); + // // Assert + // expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config, {}); + // }); + // }); describe('logoff', () => { it('logs and returns if `endSessionUrl` is false', () => { // Arrange - spyOn(service, 'getEndSessionUrl').and.returnValue(''); + spyOn(urlService, 'getEndSessionUrl').and.returnValue(''); const serverStateChangedSpy = spyOn(checkSessionService, 'serverStateChanged'); const config = { configId: 'configId1' }; @@ -413,7 +413,7 @@ describe('Logout and Revoke Service', () => { it('logs and returns if `serverStateChanged` is true', () => { // Arrange - spyOn(service, 'getEndSessionUrl').and.returnValue('someValue'); + spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); const redirectSpy = spyOn(redirectService, 'redirectTo'); spyOn(checkSessionService, 'serverStateChanged').and.returnValue(true); @@ -427,7 +427,7 @@ describe('Logout and Revoke Service', () => { it('calls urlHandler if urlhandler is passed', () => { // Arrange - spyOn(service, 'getEndSessionUrl').and.returnValue('someValue'); + spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); const spy = jasmine.createSpy(); const urlHandler = (url): void => { spy(url); @@ -447,7 +447,7 @@ describe('Logout and Revoke Service', () => { it('calls redirect service if no urlhandler is passed', () => { // Arrange - spyOn(service, 'getEndSessionUrl').and.returnValue('someValue'); + spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); const redirectSpy = spyOn(redirectService, 'redirectTo'); diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts index 5975e9ce6..5de6db432 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts @@ -130,7 +130,7 @@ export class LogoffRevocationService { } private logout(authOptions: LogoutAuthOptions, endSessionUrl: string, config: OpenIdConfiguration): Observable { - const { logoffMethod } = authOptions; + const { logoffMethod } = authOptions || {}; if (!logoffMethod || logoffMethod === 'GET') { return of(this.redirectService.redirectTo(endSessionUrl)); diff --git a/projects/angular-auth-oidc-client/src/lib/oidc.security.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/oidc.security.service.spec.ts index b1e9f3632..055f7aca2 100644 --- a/projects/angular-auth-oidc-client/src/lib/oidc.security.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/oidc.security.service.spec.ts @@ -478,14 +478,15 @@ describe('OidcSecurityService', () => { }); describe('logoff', () => { - it('calls logoffRevocationService.logoff ', waitForAsync(() => { + it('calls logoffRevocationService.logoff', waitForAsync(() => { const config = { configId: 'configId1' }; spyOn(configurationService, 'getOpenIDConfigurations').and.returnValue(of({ allConfigs: [config], currentConfig: config })); - const spy = spyOn(logoffRevocationService, 'logoff'); + const spy = spyOn(logoffRevocationService, 'logoff').and.returnValue(of(null)); - oidcSecurityService.logoff(); - expect(spy).toHaveBeenCalledOnceWith(config, [config], undefined); + oidcSecurityService.logoff().subscribe(() => { + expect(spy).toHaveBeenCalledOnceWith(config, [config], undefined); + }); })); }); @@ -567,7 +568,7 @@ describe('OidcSecurityService', () => { spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(of(config)); - const spy = spyOn(logoffRevocationService, 'getEndSessionUrl').and.returnValue(null); + const spy = spyOn(urlService, 'getEndSessionUrl').and.returnValue(null); oidcSecurityService.getEndSessionUrl().subscribe(() => { expect(spy).toHaveBeenCalledOnceWith(config, undefined); @@ -579,7 +580,7 @@ describe('OidcSecurityService', () => { spyOn(configurationService, 'getOpenIDConfiguration').and.returnValue(of(config)); - const spy = spyOn(logoffRevocationService, 'getEndSessionUrl').and.returnValue(null); + const spy = spyOn(urlService, 'getEndSessionUrl').and.returnValue(null); oidcSecurityService.getEndSessionUrl({ custom: 'params' }).subscribe(() => { expect(spy).toHaveBeenCalledOnceWith(config, { custom: 'params' }); diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts index af1dd822b..ef5cec86e 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts @@ -1493,150 +1493,150 @@ describe('UrlService Tests', () => { })); }); - describe('createEndSessionUrl', () => { - it('create URL when all parameters given', () => { - const config = { - authority: 'https://localhost:5001', - redirectUrl: 'https://localhost:44386', - clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - responseType: 'id_token token', - scope: 'openid email profile', - postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', - }; - - spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - endSessionEndpoint: 'http://example', - }); - - const value = service.createEndSessionUrl('mytoken', config); - - const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + // describe('createEndSessionUrl', () => { + // it('create URL when all parameters given', () => { + // const config = { + // authority: 'https://localhost:5001', + // redirectUrl: 'https://localhost:44386', + // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', + // responseType: 'id_token token', + // scope: 'openid email profile', + // postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + // }; + + // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + // endSessionEndpoint: 'http://example', + // }); - expect(value).toEqual(expectValue); - }); - - it('create URL when all parameters given but no idTokenHint', () => { - const config = { - authority: 'https://localhost:5001', - redirectUrl: 'https://localhost:44386', - clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - responseType: 'id_token token', - scope: 'openid email profile', - postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', - }; + // const value = service.createEndSessionUrl('mytoken', config); + + // const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; - spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - endSessionEndpoint: 'http://example', - }); + // expect(value).toEqual(expectValue); + // }); + + // it('create URL when all parameters given but no idTokenHint', () => { + // const config = { + // authority: 'https://localhost:5001', + // redirectUrl: 'https://localhost:44386', + // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', + // responseType: 'id_token token', + // scope: 'openid email profile', + // postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + // }; - const value = service.createEndSessionUrl(null, config); + // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + // endSessionEndpoint: 'http://example', + // }); + + // const value = service.createEndSessionUrl(null, config); - const expectValue = 'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + // const expectValue = 'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; - expect(value).toEqual(expectValue); - }); + // expect(value).toEqual(expectValue); + // }); - it('create URL when all parameters and customParamsEndSession given', () => { - const config = { - authority: 'https://localhost:5001', - redirectUrl: 'https://localhost:44386', - clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - responseType: 'id_token token', - scope: 'openid email profile', - postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', - }; + // it('create URL when all parameters and customParamsEndSession given', () => { + // const config = { + // authority: 'https://localhost:5001', + // redirectUrl: 'https://localhost:44386', + // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', + // responseType: 'id_token token', + // scope: 'openid email profile', + // postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + // }; - spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - endSessionEndpoint: 'http://example', - }); + // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + // endSessionEndpoint: 'http://example', + // }); - const value = service.createEndSessionUrl('mytoken', config, { param: 'to-add' }); + // const value = service.createEndSessionUrl('mytoken', config, { param: 'to-add' }); - const expectValue = - 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized¶m=to-add'; + // const expectValue = + // 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized¶m=to-add'; - expect(value).toEqual(expectValue); - }); + // expect(value).toEqual(expectValue); + // }); - it('with azure-ad-b2c policy parameter', () => { - const config = { authority: 'https://localhost:5001' } as OpenIdConfiguration; + // it('with azure-ad-b2c policy parameter', () => { + // const config = { authority: 'https://localhost:5001' } as OpenIdConfiguration; - config.redirectUrl = 'https://localhost:44386'; - config.clientId = 'myid'; - config.responseType = 'id_token token'; - config.scope = 'openid email profile'; - config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized'; + // config.redirectUrl = 'https://localhost:44386'; + // config.clientId = 'myid'; + // config.responseType = 'id_token token'; + // config.scope = 'openid email profile'; + // config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized'; - const endSessionEndpoint = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in'; + // const endSessionEndpoint = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in'; - spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - endSessionEndpoint, - }); - const value = service.createEndSessionUrl('UzI1NiIsImtpZCI6Il', config); + // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + // endSessionEndpoint, + // }); + // const value = service.createEndSessionUrl('UzI1NiIsImtpZCI6Il', config); - const expectValue = - 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' + - '&id_token_hint=UzI1NiIsImtpZCI6Il' + - '&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + // const expectValue = + // 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' + + // '&id_token_hint=UzI1NiIsImtpZCI6Il' + + // '&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; - expect(value).toEqual(expectValue); - }); + // expect(value).toEqual(expectValue); + // }); - it('create URL without postLogoutRedirectUri when not given', () => { - const config = { - authority: 'https://localhost:5001', - redirectUrl: 'https://localhost:44386', - clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - responseType: 'id_token token', - scope: 'openid email profile', - postLogoutRedirectUri: null, - }; + // it('create URL without postLogoutRedirectUri when not given', () => { + // const config = { + // authority: 'https://localhost:5001', + // redirectUrl: 'https://localhost:44386', + // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', + // responseType: 'id_token token', + // scope: 'openid email profile', + // postLogoutRedirectUri: null, + // }; - spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - endSessionEndpoint: 'http://example', - }); + // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + // endSessionEndpoint: 'http://example', + // }); - const value = service.createEndSessionUrl('mytoken', config); + // const value = service.createEndSessionUrl('mytoken', config); - const expectValue = 'http://example?id_token_hint=mytoken'; + // const expectValue = 'http://example?id_token_hint=mytoken'; - expect(value).toEqual(expectValue); - }); + // expect(value).toEqual(expectValue); + // }); - it('returns null if no wellknownEndpoints given', () => { - const value = service.createEndSessionUrl('mytoken', {}); + // it('returns null if no wellknownEndpoints given', () => { + // const value = service.createEndSessionUrl('mytoken', {}); - const expectValue = null; + // const expectValue = null; - expect(value).toEqual(expectValue); - }); + // expect(value).toEqual(expectValue); + // }); - it('returns null if no wellknownEndpoints.endSessionEndpoint given', () => { - spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', {}).and.returnValue({ - endSessionEndpoint: null, - }); + // it('returns null if no wellknownEndpoints.endSessionEndpoint given', () => { + // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', {}).and.returnValue({ + // endSessionEndpoint: null, + // }); - const value = service.createEndSessionUrl('mytoken', {}); + // const value = service.createEndSessionUrl('mytoken', {}); - const expectValue = null; + // const expectValue = null; - expect(value).toEqual(expectValue); - }); + // expect(value).toEqual(expectValue); + // }); - it('returns auth0 format URL if authority ends with .auth0', () => { - const config = { - authority: 'something.auth0.com', - clientId: 'someClientId', - postLogoutRedirectUri: 'https://localhost:1234/unauthorized', - }; + // it('returns auth0 format URL if authority ends with .auth0', () => { + // const config = { + // authority: 'something.auth0.com', + // clientId: 'someClientId', + // postLogoutRedirectUri: 'https://localhost:1234/unauthorized', + // }; - const value = service.createEndSessionUrl('anything', config); + // const value = service.createEndSessionUrl('anything', config); - const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`; + // const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`; - expect(value).toEqual(expectValue); - }); - }); + // expect(value).toEqual(expectValue); + // }); + // }); describe('getAuthorizeParUrl', () => { it('returns null if authWellKnownEndPoints is undefined', () => { diff --git a/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts index 09d560f48..a1ae02998 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/state-validation.service.spec.ts @@ -1530,7 +1530,7 @@ describe('State Validation Service', () => { isValidObs$.subscribe((isValid) => { expect(isValid.state).toBe(ValidationResult.Ok); - expect(isValid.authResponseIsValid).toBe(false); + expect(isValid.authResponseIsValid).toBe(true); }); })); @@ -1559,7 +1559,7 @@ describe('State Validation Service', () => { isValidObs$.subscribe((isValid) => { expect(isValid.state).toBe(ValidationResult.Ok); - expect(isValid.authResponseIsValid).toBe(false); + expect(isValid.authResponseIsValid).toBe(true); }); })); @@ -1588,7 +1588,7 @@ describe('State Validation Service', () => { isValidObs$.subscribe((isValid) => { expect(isValid.state).toBe(ValidationResult.Ok); - expect(isValid.authResponseIsValid).toBe(false); + expect(isValid.authResponseIsValid).toBe(true); }); })); }); From bab5a055cb65e6758c6f00dc7e11eb18e6f24361 Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Tue, 15 Nov 2022 20:13:09 +0100 Subject: [PATCH 3/9] Adding Tests --- .../logoff-revocation.service.spec.ts | 16 -- .../src/lib/utils/url/url.service.spec.ts | 228 ++++++++---------- .../src/lib/utils/url/url.service.ts | 14 +- 3 files changed, 116 insertions(+), 142 deletions(-) diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts index ca2ce0579..606b61370 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts @@ -382,22 +382,6 @@ describe('Logout and Revoke Service', () => { })); }); - // describe('getEndSessionUrl', () => { - // it('uses id_token parameter from persistence if no param is provided', () => { - // // Arrange - // const paramToken = 'damienId'; - - // spyOn(storagePersistenceService, 'getIdToken').and.returnValue(paramToken); - // const revocationSpy = spyOn(urlService, 'createEndSessionUrl'); - // const config = { configId: 'configId1' }; - - // // Act - // service.getEndSessionUrl(config); - // // Assert - // expect(revocationSpy).toHaveBeenCalledOnceWith(paramToken, config, {}); - // }); - // }); - describe('logoff', () => { it('logs and returns if `endSessionUrl` is false', () => { // Arrange diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts index ef5cec86e..77563bda4 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts @@ -1493,150 +1493,132 @@ describe('UrlService Tests', () => { })); }); - // describe('createEndSessionUrl', () => { - // it('create URL when all parameters given', () => { - // const config = { - // authority: 'https://localhost:5001', - // redirectUrl: 'https://localhost:44386', - // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - // responseType: 'id_token token', - // scope: 'openid email profile', - // postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', - // }; - - // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - // endSessionEndpoint: 'http://example', - // }); - - // const value = service.createEndSessionUrl('mytoken', config); - - // const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; - - // expect(value).toEqual(expectValue); - // }); - - // it('create URL when all parameters given but no idTokenHint', () => { - // const config = { - // authority: 'https://localhost:5001', - // redirectUrl: 'https://localhost:44386', - // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - // responseType: 'id_token token', - // scope: 'openid email profile', - // postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', - // }; - - // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - // endSessionEndpoint: 'http://example', - // }); - - // const value = service.createEndSessionUrl(null, config); - - // const expectValue = 'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; - - // expect(value).toEqual(expectValue); - // }); - - // it('create URL when all parameters and customParamsEndSession given', () => { - // const config = { - // authority: 'https://localhost:5001', - // redirectUrl: 'https://localhost:44386', - // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - // responseType: 'id_token token', - // scope: 'openid email profile', - // postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', - // }; - - // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - // endSessionEndpoint: 'http://example', - // }); - - // const value = service.createEndSessionUrl('mytoken', config, { param: 'to-add' }); - - // const expectValue = - // 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized¶m=to-add'; - - // expect(value).toEqual(expectValue); - // }); - - // it('with azure-ad-b2c policy parameter', () => { - // const config = { authority: 'https://localhost:5001' } as OpenIdConfiguration; + describe('getEndSessionUrl', () => { + it('create URL when all parameters given', () => { + //Arrange + const config = { + postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + endSessionEndpoint: 'http://example', + }); - // config.redirectUrl = 'https://localhost:44386'; - // config.clientId = 'myid'; - // config.responseType = 'id_token token'; - // config.scope = 'openid email profile'; - // config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized'; + // Act + const value = service.getEndSessionUrl(config); - // const endSessionEndpoint = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in'; + // Assert + const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + expect(value).toEqual(expectValue); + }); - // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - // endSessionEndpoint, - // }); - // const value = service.createEndSessionUrl('UzI1NiIsImtpZCI6Il', config); + it('create URL when all parameters given but no idTokenHint', () => { + // Arrange + const config = { + postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'getIdToken').and.returnValue(null); + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + endSessionEndpoint: 'http://example', + }); - // const expectValue = - // 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' + - // '&id_token_hint=UzI1NiIsImtpZCI6Il' + - // '&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + // Act + const value = service.getEndSessionUrl(config); - // expect(value).toEqual(expectValue); - // }); + // Assert + const expectValue = 'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + expect(value).toEqual(expectValue); + }); - // it('create URL without postLogoutRedirectUri when not given', () => { - // const config = { - // authority: 'https://localhost:5001', - // redirectUrl: 'https://localhost:44386', - // clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', - // responseType: 'id_token token', - // scope: 'openid email profile', - // postLogoutRedirectUri: null, - // }; + it('create URL when all parameters and customParamsEndSession given', () => { + // Arrange + const config = { + postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + endSessionEndpoint: 'http://example', + }); - // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ - // endSessionEndpoint: 'http://example', - // }); + // Act + const value = service.getEndSessionUrl(config, { param: 'to-add' }); - // const value = service.createEndSessionUrl('mytoken', config); + // Assert + const expectValue = + 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized¶m=to-add'; + expect(value).toEqual(expectValue); + }); - // const expectValue = 'http://example?id_token_hint=mytoken'; + it('with azure-ad-b2c policy parameter', () => { + // Arrange + const config = { + postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', + } as OpenIdConfiguration; + const endSessionEndpoint = 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in'; - // expect(value).toEqual(expectValue); - // }); + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + endSessionEndpoint, + }); + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('UzI1NiIsImtpZCI6Il'); - // it('returns null if no wellknownEndpoints given', () => { - // const value = service.createEndSessionUrl('mytoken', {}); + // Act + const value = service.getEndSessionUrl(config); - // const expectValue = null; + // Assert + const expectValue = + 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' + + '&id_token_hint=UzI1NiIsImtpZCI6Il' + + '&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + expect(value).toEqual(expectValue); + }); - // expect(value).toEqual(expectValue); - // }); + it('create URL without postLogoutRedirectUri when not given', () => { + const config = { + postLogoutRedirectUri: null, + } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ + endSessionEndpoint: 'http://example', + }); + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); - // it('returns null if no wellknownEndpoints.endSessionEndpoint given', () => { - // spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', {}).and.returnValue({ - // endSessionEndpoint: null, - // }); + // Act + const value = service.getEndSessionUrl(config); - // const value = service.createEndSessionUrl('mytoken', {}); + // Assert + const expectValue = 'http://example?id_token_hint=mytoken'; + expect(value).toEqual(expectValue); + }); - // const expectValue = null; + it('returns null if no wellknownEndpoints.endSessionEndpoint given', () => { + // Arrange + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', {}).and.returnValue({ + endSessionEndpoint: null, + }); + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); - // expect(value).toEqual(expectValue); - // }); + // Act + const value = service.getEndSessionUrl({}); - // it('returns auth0 format URL if authority ends with .auth0', () => { - // const config = { - // authority: 'something.auth0.com', - // clientId: 'someClientId', - // postLogoutRedirectUri: 'https://localhost:1234/unauthorized', - // }; + // Assert + expect(value).toEqual(null); + }); - // const value = service.createEndSessionUrl('anything', config); + it('returns auth0 format URL if authority ends with .auth0', () => { + // Arrange + const config = { + authority: 'something.auth0.com', + clientId: 'someClientId', + postLogoutRedirectUri: 'https://localhost:1234/unauthorized', + }; - // const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`; + // Act + const value = service.getEndSessionUrl(config); - // expect(value).toEqual(expectValue); - // }); - // }); + // Assert + const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`; + expect(value).toEqual(expectValue); + }); + }); describe('getAuthorizeParUrl', () => { it('returns null if authWellKnownEndPoints is undefined', () => { diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts index dc56a2281..84a92a67f 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.ts @@ -107,12 +107,15 @@ export class UrlService { const endSessionEndpoint = authWellKnownEndPoints?.endSessionEndpoint; if (!endSessionEndpoint) { - return null; + return { + url: '', + existingParams: '', + }; } const urlParts = endSessionEndpoint.split('?'); const url = urlParts[0]; - const existingParams = urlParts[1]; + const existingParams = urlParts[1] ?? ''; return { url, @@ -317,7 +320,7 @@ export class UrlService { idTokenHint: string, configuration: OpenIdConfiguration, customParamsEndSession?: { [p: string]: string | number | boolean } - ): string { + ): string | null { // Auth0 needs a special logout url // See https://auth0.com/docs/api/authentication#logout @@ -326,6 +329,11 @@ export class UrlService { } const { url, existingParams } = this.getEndSessionEndpoint(configuration); + + if (!url) { + return null; + } + let params = this.createHttpParams(existingParams); if (!!idTokenHint) { From d60a25c393a5104c67bcca3e6e8d8c99b4fdeb85 Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Tue, 15 Nov 2022 20:59:53 +0100 Subject: [PATCH 4/9] Fixed testing --- .../src/lib/auth.module.ts | 6 +- .../src/lib/extractors/jwk.extractor.spec.ts | 284 +++++++++--------- .../src/lib/flows/flows-data.service.spec.ts | 2 +- .../lib/flows/random/random.service.spec.ts | 2 +- .../src/lib/flows/random/random.service.ts | 2 +- .../logoff-revocation.service.spec.ts | 146 +++++++-- .../logoff-revocation.service.ts | 19 +- ...service.spec.ts => crypto.service.spec.ts} | 2 +- .../{crypto-service.ts => crypto.service.ts} | 0 .../src/lib/utils/object/object.helper.ts | 10 + .../jwk-window-crypto.service.spec.ts | 84 +++--- .../validation/jwk-window-crypto.service.ts | 16 +- .../jwt-window-crypto.service.spec.ts | 19 +- .../validation/jwt-window-crypto.service.ts | 2 +- .../token-validation.service.spec.ts | 2 +- 15 files changed, 359 insertions(+), 237 deletions(-) rename projects/angular-auth-oidc-client/src/lib/utils/crypto/{crypto-service.spec.ts => crypto.service.spec.ts} (88%) rename projects/angular-auth-oidc-client/src/lib/utils/crypto/{crypto-service.ts => crypto.service.ts} (100%) create mode 100644 projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts diff --git a/projects/angular-auth-oidc-client/src/lib/auth.module.ts b/projects/angular-auth-oidc-client/src/lib/auth.module.ts index b03147859..75f81cd3e 100644 --- a/projects/angular-auth-oidc-client/src/lib/auth.module.ts +++ b/projects/angular-auth-oidc-client/src/lib/auth.module.ts @@ -13,6 +13,7 @@ import { ConfigurationService } from './config/config.service'; import { StsConfigLoader, StsConfigStaticLoader } from './config/loader/config-loader'; import { OpenIdConfiguration } from './config/openid-configuration'; import { ConfigValidationService } from './config/validation/config-validation.service'; +import { JwkExtractor } from './extractors/jwk.extractor'; import { CodeFlowCallbackHandlerService } from './flows/callback-handling/code-flow-callback-handler.service'; import { HistoryJwtKeysCallbackHandlerService } from './flows/callback-handling/history-jwt-keys-callback-handler.service'; import { ImplicitFlowCallbackHandlerService } from './flows/callback-handling/implicit-flow-callback-handler.service'; @@ -46,18 +47,17 @@ import { BrowserStorageService } from './storage/browser-storage.service'; import { DefaultSessionStorageService } from './storage/default-sessionstorage.service'; import { StoragePersistenceService } from './storage/storage-persistence.service'; import { UserService } from './user-data/user.service'; -import { CryptoService } from './utils/crypto/crypto-service'; +import { CryptoService } from './utils/crypto/crypto.service'; import { EqualityService } from './utils/equality/equality.service'; import { FlowHelper } from './utils/flowHelper/flow-helper.service'; import { PlatformProvider } from './utils/platform-provider/platform.provider'; import { TokenHelperService } from './utils/tokenHelper/token-helper.service'; import { CurrentUrlService } from './utils/url/current-url.service'; import { UrlService } from './utils/url/url.service'; +import { JwkWindowCryptoService } from './validation/jwk-window-crypto.service'; import { JwtWindowCryptoService } from './validation/jwt-window-crypto.service'; import { StateValidationService } from './validation/state-validation.service'; import { TokenValidationService } from './validation/token-validation.service'; -import { JwkExtractor } from './extractors/jwk.extractor'; -import { JwkWindowCryptoService } from './validation/jwk-window-crypto.service'; export interface PassedInitialConfig { config?: OpenIdConfiguration | OpenIdConfiguration[]; diff --git a/projects/angular-auth-oidc-client/src/lib/extractors/jwk.extractor.spec.ts b/projects/angular-auth-oidc-client/src/lib/extractors/jwk.extractor.spec.ts index 978b666f5..859e3ed2e 100644 --- a/projects/angular-auth-oidc-client/src/lib/extractors/jwk.extractor.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/extractors/jwk.extractor.spec.ts @@ -1,75 +1,75 @@ import { TestBed } from '@angular/core/testing'; -import { CryptoService } from '../utils/crypto/crypto-service'; +import { CryptoService } from '../utils/crypto/crypto.service'; import { JwkExtractor } from './jwk.extractor'; describe('JwkExtractor', () => { let service: JwkExtractor; const key1 = { - "kty": "RSA", - "use": "sig", - "kid": "5626CE6A8F4F5FCD79C6642345282CA76D337548RS256", - "x5t": "VibOao9PX815xmQjRSgsp20zdUg", - "e": "AQAB", - "n": "uu3-HK4pLRHJHoEBzFhM516RWx6nybG5yQjH4NbKjfGQ8dtKy1BcGjqfMaEKF8KOK44NbAx7rtBKCO9EKNYkeFvcUzBzVeuu4jWG61XYdTekgv-Dh_Fj8245GocEkbvBbFW6cw-_N59JWqUuiCvb-EOfhcuubUcr44a0AQyNccYNpcXGRcMKy7_L1YhO0AMULqLDDVLFj5glh4TcJ2N5VnJedq1-_JKOxPqD1ni26UOQoWrW16G29KZ1_4Xxf2jX8TAq-4RJEHccdzgZVIO4F5B4MucMZGq8_jMCpiTUsUGDOAMA_AmjxIRHOtO5n6Pt0wofrKoAVhGh2sCTtaQf2Q", - "x5c": [ - "MIIDPzCCAiegAwIBAgIQF+HRVxLHII9IlOoQk6BxcjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBzdHMuZGV2LmNlcnQuY29tMB4XDTE5MDIyMDEwMTA0M1oXDTM5MDIyMDEwMTkyOVowGzEZMBcGA1UEAwwQc3RzLmRldi5jZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrt/hyuKS0RyR6BAcxYTOdekVsep8mxuckIx+DWyo3xkPHbSstQXBo6nzGhChfCjiuODWwMe67QSgjvRCjWJHhb3FMwc1XrruI1hutV2HU3pIL/g4fxY/NuORqHBJG7wWxVunMPvzefSVqlLogr2/hDn4XLrm1HK+OGtAEMjXHGDaXFxkXDCsu/y9WITtADFC6iww1SxY+YJYeE3CdjeVZyXnatfvySjsT6g9Z4tulDkKFq1tehtvSmdf+F8X9o1/EwKvuESRB3HHc4GVSDuBeQeDLnDGRqvP4zAqYk1LFBgzgDAPwJo8SERzrTuZ+j7dMKH6yqAFYRodrAk7WkH9kCAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAtBgNVHREEJjAkghBzdHMuZGV2LmNlcnQuY29tghBzdHMuZGV2LmNlcnQuY29tMB0GA1UdDgQWBBQuyHxWP3je6jGMOmOiY+hz47r36jANBgkqhkiG9w0BAQsFAAOCAQEAKEHG7Ga6nb2XiHXDc69KsIJwbO80+LE8HVJojvITILz3juN6/FmK0HmogjU6cYST7m1MyxsVhQQNwJASZ6haBNuBbNzBXfyyfb4kr62t1oDLNwhctHaHaM4sJSf/xIw+YO+Qf7BtfRAVsbM05+QXIi2LycGrzELiXu7KFM0E1+T8UOZ2Qyv7OlCb/pWkYuDgE4w97ox0MhDpvgluxZLpRanOLUCVGrfFaij7gRAhjYPUY3vAEcD8JcFBz1XijU8ozRO6FaG4qg8/JCe+VgoWsMDj3sKB9g0ob6KCyG9L2bdk99PGgvXDQvMYCpkpZzG3XsxOINPd5p0gc209ZOoxTg==" + kty: 'RSA', + use: 'sig', + kid: '5626CE6A8F4F5FCD79C6642345282CA76D337548RS256', + x5t: 'VibOao9PX815xmQjRSgsp20zdUg', + e: 'AQAB', + n: 'uu3-HK4pLRHJHoEBzFhM516RWx6nybG5yQjH4NbKjfGQ8dtKy1BcGjqfMaEKF8KOK44NbAx7rtBKCO9EKNYkeFvcUzBzVeuu4jWG61XYdTekgv-Dh_Fj8245GocEkbvBbFW6cw-_N59JWqUuiCvb-EOfhcuubUcr44a0AQyNccYNpcXGRcMKy7_L1YhO0AMULqLDDVLFj5glh4TcJ2N5VnJedq1-_JKOxPqD1ni26UOQoWrW16G29KZ1_4Xxf2jX8TAq-4RJEHccdzgZVIO4F5B4MucMZGq8_jMCpiTUsUGDOAMA_AmjxIRHOtO5n6Pt0wofrKoAVhGh2sCTtaQf2Q', + x5c: [ + 'MIIDPzCCAiegAwIBAgIQF+HRVxLHII9IlOoQk6BxcjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBzdHMuZGV2LmNlcnQuY29tMB4XDTE5MDIyMDEwMTA0M1oXDTM5MDIyMDEwMTkyOVowGzEZMBcGA1UEAwwQc3RzLmRldi5jZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrt/hyuKS0RyR6BAcxYTOdekVsep8mxuckIx+DWyo3xkPHbSstQXBo6nzGhChfCjiuODWwMe67QSgjvRCjWJHhb3FMwc1XrruI1hutV2HU3pIL/g4fxY/NuORqHBJG7wWxVunMPvzefSVqlLogr2/hDn4XLrm1HK+OGtAEMjXHGDaXFxkXDCsu/y9WITtADFC6iww1SxY+YJYeE3CdjeVZyXnatfvySjsT6g9Z4tulDkKFq1tehtvSmdf+F8X9o1/EwKvuESRB3HHc4GVSDuBeQeDLnDGRqvP4zAqYk1LFBgzgDAPwJo8SERzrTuZ+j7dMKH6yqAFYRodrAk7WkH9kCAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAtBgNVHREEJjAkghBzdHMuZGV2LmNlcnQuY29tghBzdHMuZGV2LmNlcnQuY29tMB0GA1UdDgQWBBQuyHxWP3je6jGMOmOiY+hz47r36jANBgkqhkiG9w0BAQsFAAOCAQEAKEHG7Ga6nb2XiHXDc69KsIJwbO80+LE8HVJojvITILz3juN6/FmK0HmogjU6cYST7m1MyxsVhQQNwJASZ6haBNuBbNzBXfyyfb4kr62t1oDLNwhctHaHaM4sJSf/xIw+YO+Qf7BtfRAVsbM05+QXIi2LycGrzELiXu7KFM0E1+T8UOZ2Qyv7OlCb/pWkYuDgE4w97ox0MhDpvgluxZLpRanOLUCVGrfFaij7gRAhjYPUY3vAEcD8JcFBz1XijU8ozRO6FaG4qg8/JCe+VgoWsMDj3sKB9g0ob6KCyG9L2bdk99PGgvXDQvMYCpkpZzG3XsxOINPd5p0gc209ZOoxTg==', ], - "alg": "RS256" + alg: 'RS256', } as JsonWebKey; const key2 = { - "kty": "RSA", - "kid": "boop", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'RSA', + kid: 'boop', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key3 = { - "kty": "RSA", - "use": "enc", - "kid": "boop", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'RSA', + use: 'enc', + kid: 'boop', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key4 = { - "kty": "RSA", - "use": "sig", - "kid": "boop", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'RSA', + use: 'sig', + kid: 'boop', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key5 = { - "kty": "RSA", - "use": "sig", - "kid": "boop", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'RSA', + use: 'sig', + kid: 'boop', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key6 = { - "kty": "EC", - "use": "sig", - "kid": "boop", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'EC', + use: 'sig', + kid: 'boop', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key7 = { - "kty": "EC", - "use": "enc", - "kid": "boop", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'EC', + use: 'enc', + kid: 'boop', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key8 = { - "kty": "EC", - "use": "sig", - "kid": "5626CE6A8F4F5FCD79C6642345282CA76D337548RS256", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'EC', + use: 'sig', + kid: '5626CE6A8F4F5FCD79C6642345282CA76D337548RS256', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key9 = { - "kty": "EC", - "use": "sig", - "kid": "5626CE6A8F4F5FCD79C6642345282CA76D337548RS256", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'EC', + use: 'sig', + kid: '5626CE6A8F4F5FCD79C6642345282CA76D337548RS256', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; let keys: JsonWebKey[] = [key1, key2, key3, key4, key5, key6, key7, key8, key9]; @@ -90,133 +90,123 @@ describe('JwkExtractor', () => { describe('extractJwk', () => { it('throws error if no keys are present in array', () => { - expect(() => { - service.extractJwk([]); - }).toThrow(JwkExtractor.InvalidArgumentError); - } - ); + expect(() => { + service.extractJwk([]); + }).toThrow(JwkExtractor.InvalidArgumentError); + }); it('throws error if spec.kid is present, but no key was matching', () => { - expect(() => { - service.extractJwk(keys, {kid: 'doot'}); - }).toThrow(JwkExtractor.NoMatchingKeysError); - } - ); + expect(() => { + service.extractJwk(keys, { kid: 'doot' }); + }).toThrow(JwkExtractor.NoMatchingKeysError); + }); it('throws error if spec.use is present, but no key was matching', () => { - expect(() => { - service.extractJwk(keys, {use: 'blorp'}); - }).toThrow(JwkExtractor.NoMatchingKeysError); - } - ); + expect(() => { + service.extractJwk(keys, { use: 'blorp' }); + }).toThrow(JwkExtractor.NoMatchingKeysError); + }); it('does not throw error if no key is matching when throwOnEmpty is false', () => { - const result = service.extractJwk(keys, {use: 'blorp'}, false); + const result = service.extractJwk(keys, { use: 'blorp' }, false); - expect(result.length).toEqual(0); - } - ); + expect(result.length).toEqual(0); + }); it('throws error if multiple keys are present, and spec is not present', () => { - expect(() => { - service.extractJwk(keys); - }).toThrow(JwkExtractor.SeveralMatchingKeysError); - } - ); + expect(() => { + service.extractJwk(keys); + }).toThrow(JwkExtractor.SeveralMatchingKeysError); + }); it('returns array of keys matching spec.kid', () => { - const extracted = service.extractJwk(keys, {kid: '5626CE6A8F4F5FCD79C6642345282CA76D337548RS256'}); - - expect(extracted.length).toEqual(3); - expect(extracted).toContain(key1); - expect(extracted).toContain(key8); - expect(extracted).toContain(key9); - - const extracted2 = service.extractJwk(keys, {kid: 'boop'}); - - expect(extracted2.length).toEqual(6); - expect(extracted2).toContain(key2); - expect(extracted2).toContain(key3); - expect(extracted2).toContain(key4); - expect(extracted2).toContain(key5); - expect(extracted2).toContain(key6); - expect(extracted2).toContain(key7); - } - ); + const extracted = service.extractJwk(keys, { kid: '5626CE6A8F4F5FCD79C6642345282CA76D337548RS256' }); + + expect(extracted.length).toEqual(3); + expect(extracted).toContain(key1); + expect(extracted).toContain(key8); + expect(extracted).toContain(key9); + + const extracted2 = service.extractJwk(keys, { kid: 'boop' }); + + expect(extracted2.length).toEqual(6); + expect(extracted2).toContain(key2); + expect(extracted2).toContain(key3); + expect(extracted2).toContain(key4); + expect(extracted2).toContain(key5); + expect(extracted2).toContain(key6); + expect(extracted2).toContain(key7); + }); it('returns array of keys matching spec.use', () => { - const extracted = service.extractJwk(keys, {use: 'sig'}); + const extracted = service.extractJwk(keys, { use: 'sig' }); - expect(extracted.length).toEqual(6); - expect(extracted).toContain(key1); - expect(extracted).toContain(key4); - expect(extracted).toContain(key5); - expect(extracted).toContain(key6); - expect(extracted).toContain(key8); - expect(extracted).toContain(key9); + expect(extracted.length).toEqual(6); + expect(extracted).toContain(key1); + expect(extracted).toContain(key4); + expect(extracted).toContain(key5); + expect(extracted).toContain(key6); + expect(extracted).toContain(key8); + expect(extracted).toContain(key9); - const extracted2 = service.extractJwk(keys, {use: 'enc'}); + const extracted2 = service.extractJwk(keys, { use: 'enc' }); - expect(extracted2.length).toEqual(2); - expect(extracted2).toContain(key3); - expect(extracted2).toContain(key7); - } - ); + expect(extracted2.length).toEqual(2); + expect(extracted2).toContain(key3); + expect(extracted2).toContain(key7); + }); it('returns array of keys matching the combination of spec.use and spec.kid', () => { - const extracted = service.extractJwk(keys, {kid: 'boop', use: 'sig'}); + const extracted = service.extractJwk(keys, { kid: 'boop', use: 'sig' }); - expect(extracted.length).toEqual(3); - expect(extracted).toContain(key4); - expect(extracted).toContain(key5); - expect(extracted).toContain(key6); + expect(extracted.length).toEqual(3); + expect(extracted).toContain(key4); + expect(extracted).toContain(key5); + expect(extracted).toContain(key6); - const extracted2 = service.extractJwk(keys, {kid: 'boop', use: 'enc'}); + const extracted2 = service.extractJwk(keys, { kid: 'boop', use: 'enc' }); - expect(extracted2.length).toEqual(2); - expect(extracted2).toContain(key3); - expect(extracted2).toContain(key7); - } - ); + expect(extracted2.length).toEqual(2); + expect(extracted2).toContain(key3); + expect(extracted2).toContain(key7); + }); it('returns array of keys matching spec.kty', () => { - const extracted = service.extractJwk(keys, {kty: 'RSA'}); - - expect(extracted.length).toEqual(5); - expect(extracted).toContain(key1); - expect(extracted).toContain(key2); - expect(extracted).toContain(key3); - expect(extracted).toContain(key4); - expect(extracted).toContain(key5); - - const extracted2 = service.extractJwk(keys, {kty: 'EC'}); - - expect(extracted2.length).toEqual(4); - expect(extracted2).toContain(key6); - expect(extracted2).toContain(key7); - expect(extracted2).toContain(key8); - expect(extracted2).toContain(key9); - } - ); + const extracted = service.extractJwk(keys, { kty: 'RSA' }); + + expect(extracted.length).toEqual(5); + expect(extracted).toContain(key1); + expect(extracted).toContain(key2); + expect(extracted).toContain(key3); + expect(extracted).toContain(key4); + expect(extracted).toContain(key5); + + const extracted2 = service.extractJwk(keys, { kty: 'EC' }); + + expect(extracted2.length).toEqual(4); + expect(extracted2).toContain(key6); + expect(extracted2).toContain(key7); + expect(extracted2).toContain(key8); + expect(extracted2).toContain(key9); + }); it('returns array of keys matching the combination of spec.kty and spec.use', () => { - const extracted = service.extractJwk(keys, {kty: 'RSA', use: 'enc'}); + const extracted = service.extractJwk(keys, { kty: 'RSA', use: 'enc' }); - expect(extracted.length).toEqual(1); - expect(extracted).toContain(key3); + expect(extracted.length).toEqual(1); + expect(extracted).toContain(key3); - const extracted2 = service.extractJwk(keys, {kty: 'EC', use: 'sig'}); + const extracted2 = service.extractJwk(keys, { kty: 'EC', use: 'sig' }); - expect(extracted2.length).toEqual(3); - expect(extracted2).toContain(key6); - expect(extracted2).toContain(key8); - expect(extracted2).toContain(key9); + expect(extracted2.length).toEqual(3); + expect(extracted2).toContain(key6); + expect(extracted2).toContain(key8); + expect(extracted2).toContain(key9); - const extracted3 = service.extractJwk(keys, {kty: 'EC', use: 'enc'}); + const extracted3 = service.extractJwk(keys, { kty: 'EC', use: 'enc' }); - expect(extracted3.length).toEqual(1); - expect(extracted3).toContain(key7); - } - ); + expect(extracted3.length).toEqual(1); + expect(extracted3).toContain(key7); + }); }); }); diff --git a/projects/angular-auth-oidc-client/src/lib/flows/flows-data.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/flows/flows-data.service.spec.ts index 9d1c2a3d5..f919b73b3 100644 --- a/projects/angular-auth-oidc-client/src/lib/flows/flows-data.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/flows/flows-data.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { mockClass } from '../../test/auto-mock'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; -import { CryptoService } from '../utils/crypto/crypto-service'; +import { CryptoService } from '../utils/crypto/crypto.service'; import { FlowsDataService } from './flows-data.service'; import { RandomService } from './random/random.service'; diff --git a/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.spec.ts index 37d182e2e..b3b5f0dc6 100644 --- a/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { mockClass } from '../../../test/auto-mock'; import { LoggerService } from '../../logging/logger.service'; -import { CryptoService } from '../../utils/crypto/crypto-service'; +import { CryptoService } from '../../utils/crypto/crypto.service'; import { RandomService } from './random.service'; describe('RandomService Tests', () => { diff --git a/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.ts b/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.ts index bf4caa2e2..9d933f7a1 100644 --- a/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/flows/random/random.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { LoggerService } from '../../logging/logger.service'; -import { CryptoService } from '../../utils/crypto/crypto-service'; +import { CryptoService } from '../../utils/crypto/crypto.service'; import { OpenIdConfiguration } from './../../config/openid-configuration'; @Injectable() diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts index 606b61370..51dcbdcba 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts @@ -1,3 +1,4 @@ +import { HttpHeaders } from '@angular/common/http'; import { TestBed, waitForAsync } from '@angular/core/testing'; import { Observable, of, throwError } from 'rxjs'; import { mockClass } from '../../test/auto-mock'; @@ -383,19 +384,23 @@ describe('Logout and Revoke Service', () => { }); describe('logoff', () => { - it('logs and returns if `endSessionUrl` is false', () => { + it('logs and returns if `endSessionUrl` is false', waitForAsync(() => { // Arrange spyOn(urlService, 'getEndSessionUrl').and.returnValue(''); + const serverStateChangedSpy = spyOn(checkSessionService, 'serverStateChanged'); const config = { configId: 'configId1' }; // Act - service.logoff(config, [config]); + const result$ = service.logoff(config, [config]); + // Assert - expect(serverStateChangedSpy).not.toHaveBeenCalled(); - }); + result$.subscribe(() => { + expect(serverStateChangedSpy).not.toHaveBeenCalled(); + }); + })); - it('logs and returns if `serverStateChanged` is true', () => { + it('logs and returns if `serverStateChanged` is true', waitForAsync(() => { // Arrange spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); const redirectSpy = spyOn(redirectService, 'redirectTo'); @@ -404,12 +409,15 @@ describe('Logout and Revoke Service', () => { const config = { configId: 'configId1' }; // Act - service.logoff(config, [config]); + const result$ = service.logoff(config, [config]); + // Assert - expect(redirectSpy).not.toHaveBeenCalled(); - }); + result$.subscribe(() => { + expect(redirectSpy).not.toHaveBeenCalled(); + }); + })); - it('calls urlHandler if urlhandler is passed', () => { + it('calls urlHandler if urlhandler is passed', waitForAsync(() => { // Arrange spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); const spy = jasmine.createSpy(); @@ -422,14 +430,16 @@ describe('Logout and Revoke Service', () => { const config = { configId: 'configId1' }; // Act - service.logoff(config, [config], { urlHandler }); + const result$ = service.logoff(config, [config], { urlHandler }); // Assert - expect(redirectSpy).not.toHaveBeenCalled(); - expect(spy).toHaveBeenCalledOnceWith('someValue'); - }); + result$.subscribe(() => { + expect(redirectSpy).not.toHaveBeenCalled(); + expect(spy).toHaveBeenCalledOnceWith('someValue'); + }); + })); - it('calls redirect service if no urlhandler is passed', () => { + it('calls redirect service if no logoutOptions are passed', waitForAsync(() => { // Arrange spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); @@ -439,11 +449,113 @@ describe('Logout and Revoke Service', () => { const config = { configId: 'configId1' }; // Act - service.logoff(config, [config]); + const result$ = service.logoff(config, [config]); // Assert - expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); - }); + result$.subscribe(() => { + expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); + }); + })); + + it('calls redirect service if logoutOptions are passed and method is GET', waitForAsync(() => { + // Arrange + spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + + const redirectSpy = spyOn(redirectService, 'redirectTo'); + + spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); + const config = { configId: 'configId1' }; + + // Act + const result$ = service.logoff(config, [config], { logoffMethod: 'GET' }); + + // Assert + result$.subscribe(() => { + expect(redirectSpy).toHaveBeenCalledOnceWith('someValue'); + }); + })); + + it('calls dataservice post if logoutOptions are passed and method is POST', waitForAsync(() => { + // Arrange + spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + + const redirectSpy = spyOn(redirectService, 'redirectTo'); + + spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('id-token'); + spyOn(urlService, 'getPostLogoutRedirectUrl').and.returnValue('post-logout-redirect-url'); + spyOn(urlService, 'getEndSessionEndpoint').and.returnValue({ url: 'some-url', existingParams: '' }); + const postSpy = spyOn(dataService, 'post').and.returnValue(of(null)); + const config = { configId: 'configId1', clientId: 'clientId' }; + + // Act + const result$ = service.logoff(config, [config], { logoffMethod: 'POST' }); + + // Assert + result$.subscribe(() => { + expect(redirectSpy).not.toHaveBeenCalled(); + expect(postSpy).toHaveBeenCalledOnceWith( + 'some-url', + { + id_token_hint: 'id-token', + client_id: 'clientId', + post_logout_redirect_uri: 'post-logout-redirect-url', + }, + config, + jasmine.anything() + ); + + const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.get('Content-Type')).toBe('application/x-www-form-urlencoded'); + }); + })); + + it('calls dataservice post if logoutOptions with customParams are passed and method is POST', waitForAsync(() => { + // Arrange + spyOn(urlService, 'getEndSessionUrl').and.returnValue('someValue'); + + const redirectSpy = spyOn(redirectService, 'redirectTo'); + + spyOn(checkSessionService, 'serverStateChanged').and.returnValue(false); + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('id-token'); + spyOn(urlService, 'getPostLogoutRedirectUrl').and.returnValue('post-logout-redirect-url'); + spyOn(urlService, 'getEndSessionEndpoint').and.returnValue({ url: 'some-url', existingParams: '' }); + const postSpy = spyOn(dataService, 'post').and.returnValue(of(null)); + const config = { configId: 'configId1', clientId: 'clientId' }; + + // Act + const result$ = service.logoff(config, [config], { + logoffMethod: 'POST', + customParams: { + state: 'state', + logout_hint: 'logoutHint', + ui_locales: 'de fr en', + }, + }); + + // Assert + result$.subscribe(() => { + expect(redirectSpy).not.toHaveBeenCalled(); + expect(postSpy).toHaveBeenCalledOnceWith( + 'some-url', + { + id_token_hint: 'id-token', + client_id: 'clientId', + post_logout_redirect_uri: 'post-logout-redirect-url', + state: 'state', + logout_hint: 'logoutHint', + ui_locales: 'de fr en', + }, + config, + jasmine.anything() + ); + + const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTrue(); + expect(httpHeaders.get('Content-Type')).toBe('application/x-www-form-urlencoded'); + }); + })); }); describe('logoffLocal', () => { diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts index 5de6db432..6e7fd8818 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts @@ -9,6 +9,7 @@ import { ResetAuthDataService } from '../flows/reset-auth-data.service'; import { CheckSessionService } from '../iframe/check-session.service'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; +import { removeNullAndUndefinedValues } from '../utils/object/object.helper'; import { RedirectService } from '../utils/redirect/redirect.service'; import { UrlService } from '../utils/url/url.service'; @@ -27,13 +28,13 @@ export class LogoffRevocationService { // Logs out on the server and the local client. // If the server state has changed, check session, then only a local logout. logoff(config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[], logoutAuthOptions?: LogoutAuthOptions): Observable { - this.loggerService.logDebug(config, 'logoff, remove auth'); + this.loggerService.logDebug(config, 'logoff, remove auth', logoutAuthOptions); const { urlHandler, customParams } = logoutAuthOptions || {}; - const endSessionUrl = this.urlService.getEndSessionUrl(config, customParams); - this.resetAuthDataService.resetAuthorizationData(config, allConfigs); + const endSessionUrl = this.urlService.getEndSessionUrl(config, customParams); + if (!endSessionUrl) { this.loggerService.logDebug(config, 'No endsessionUrl present. Logoff was only locally. Returning.'); @@ -53,7 +54,7 @@ export class LogoffRevocationService { return of(null); } - return this.logout(logoutAuthOptions, endSessionUrl, config); + return this.logoffInternal(logoutAuthOptions, endSessionUrl, config); } logoffLocal(config: OpenIdConfiguration, allConfigs: OpenIdConfiguration[]): void { @@ -129,14 +130,14 @@ export class LogoffRevocationService { return this.sendRevokeRequest(configuration, body); } - private logout(authOptions: LogoutAuthOptions, endSessionUrl: string, config: OpenIdConfiguration): Observable { - const { logoffMethod } = authOptions || {}; + private logoffInternal(logoutAuthOptions: LogoutAuthOptions, endSessionUrl: string, config: OpenIdConfiguration): Observable { + const { logoffMethod, customParams } = logoutAuthOptions || {}; if (!logoffMethod || logoffMethod === 'GET') { return of(this.redirectService.redirectTo(endSessionUrl)); } - const { state, logout_hint, ui_locales } = authOptions?.customParams || {}; + const { state, logout_hint, ui_locales } = customParams || {}; const { clientId } = config; const idToken = this.storagePersistenceService.getIdToken(config); const postLogoutRedirectUrl = this.urlService.getPostLogoutRedirectUrl(config); @@ -151,7 +152,9 @@ export class LogoffRevocationService { ui_locales, }; - return this.dataService.post(url, body, config, headers); + const bodyWithoutNullOrUndefined = removeNullAndUndefinedValues(body); + + return this.dataService.post(url, bodyWithoutNullOrUndefined, config, headers); } private sendRevokeRequest(configuration: OpenIdConfiguration, body: string): Observable { diff --git a/projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto-service.spec.ts b/projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto.service.spec.ts similarity index 88% rename from projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto-service.spec.ts rename to projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto.service.spec.ts index dbd6bc9d1..fb5c05d62 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto-service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@angular/core/testing'; -import { CryptoService } from './crypto-service'; +import { CryptoService } from './crypto.service'; describe('CryptoService Tests', () => { let cryptoService: CryptoService; diff --git a/projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto-service.ts b/projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto.service.ts similarity index 100% rename from projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto-service.ts rename to projects/angular-auth-oidc-client/src/lib/utils/crypto/crypto.service.ts diff --git a/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts b/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts new file mode 100644 index 000000000..1bfcaabf4 --- /dev/null +++ b/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts @@ -0,0 +1,10 @@ +export function removeNullAndUndefinedValues(obj: any): any { + const copy = { ...obj }; + for (const key in obj) { + if (obj[key] === undefined || obj[key] === null) { + delete copy[key]; + } + } + + return copy; +} diff --git a/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.spec.ts index 92176b578..7f472ac8d 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed, waitForAsync } from '@angular/core/testing'; -import { CryptoService } from '../utils/crypto/crypto-service'; -import { JwkWindowCryptoService } from './jwk-window-crypto.service'; import { base64url } from 'rfc4648'; +import { CryptoService } from '../utils/crypto/crypto.service'; +import { JwkWindowCryptoService } from './jwk-window-crypto.service'; describe('JwkWindowCryptoService', () => { let service: JwkWindowCryptoService; @@ -10,29 +10,29 @@ describe('JwkWindowCryptoService', () => { hash: 'SHA-256', }; const key1 = { - "kty": "RSA", - "use": "sig", - "kid": "5626CE6A8F4F5FCD79C6642345282CA76D337548RS256", - "x5t": "VibOao9PX815xmQjRSgsp20zdUg", - "e": "AQAB", - "n": "uu3-HK4pLRHJHoEBzFhM516RWx6nybG5yQjH4NbKjfGQ8dtKy1BcGjqfMaEKF8KOK44NbAx7rtBKCO9EKNYkeFvcUzBzVeuu4jWG61XYdTekgv-Dh_Fj8245GocEkbvBbFW6cw-_N59JWqUuiCvb-EOfhcuubUcr44a0AQyNccYNpcXGRcMKy7_L1YhO0AMULqLDDVLFj5glh4TcJ2N5VnJedq1-_JKOxPqD1ni26UOQoWrW16G29KZ1_4Xxf2jX8TAq-4RJEHccdzgZVIO4F5B4MucMZGq8_jMCpiTUsUGDOAMA_AmjxIRHOtO5n6Pt0wofrKoAVhGh2sCTtaQf2Q", - "x5c": [ - "MIIDPzCCAiegAwIBAgIQF+HRVxLHII9IlOoQk6BxcjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBzdHMuZGV2LmNlcnQuY29tMB4XDTE5MDIyMDEwMTA0M1oXDTM5MDIyMDEwMTkyOVowGzEZMBcGA1UEAwwQc3RzLmRldi5jZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrt/hyuKS0RyR6BAcxYTOdekVsep8mxuckIx+DWyo3xkPHbSstQXBo6nzGhChfCjiuODWwMe67QSgjvRCjWJHhb3FMwc1XrruI1hutV2HU3pIL/g4fxY/NuORqHBJG7wWxVunMPvzefSVqlLogr2/hDn4XLrm1HK+OGtAEMjXHGDaXFxkXDCsu/y9WITtADFC6iww1SxY+YJYeE3CdjeVZyXnatfvySjsT6g9Z4tulDkKFq1tehtvSmdf+F8X9o1/EwKvuESRB3HHc4GVSDuBeQeDLnDGRqvP4zAqYk1LFBgzgDAPwJo8SERzrTuZ+j7dMKH6yqAFYRodrAk7WkH9kCAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAtBgNVHREEJjAkghBzdHMuZGV2LmNlcnQuY29tghBzdHMuZGV2LmNlcnQuY29tMB0GA1UdDgQWBBQuyHxWP3je6jGMOmOiY+hz47r36jANBgkqhkiG9w0BAQsFAAOCAQEAKEHG7Ga6nb2XiHXDc69KsIJwbO80+LE8HVJojvITILz3juN6/FmK0HmogjU6cYST7m1MyxsVhQQNwJASZ6haBNuBbNzBXfyyfb4kr62t1oDLNwhctHaHaM4sJSf/xIw+YO+Qf7BtfRAVsbM05+QXIi2LycGrzELiXu7KFM0E1+T8UOZ2Qyv7OlCb/pWkYuDgE4w97ox0MhDpvgluxZLpRanOLUCVGrfFaij7gRAhjYPUY3vAEcD8JcFBz1XijU8ozRO6FaG4qg8/JCe+VgoWsMDj3sKB9g0ob6KCyG9L2bdk99PGgvXDQvMYCpkpZzG3XsxOINPd5p0gc209ZOoxTg==" + kty: 'RSA', + use: 'sig', + kid: '5626CE6A8F4F5FCD79C6642345282CA76D337548RS256', + x5t: 'VibOao9PX815xmQjRSgsp20zdUg', + e: 'AQAB', + n: 'uu3-HK4pLRHJHoEBzFhM516RWx6nybG5yQjH4NbKjfGQ8dtKy1BcGjqfMaEKF8KOK44NbAx7rtBKCO9EKNYkeFvcUzBzVeuu4jWG61XYdTekgv-Dh_Fj8245GocEkbvBbFW6cw-_N59JWqUuiCvb-EOfhcuubUcr44a0AQyNccYNpcXGRcMKy7_L1YhO0AMULqLDDVLFj5glh4TcJ2N5VnJedq1-_JKOxPqD1ni26UOQoWrW16G29KZ1_4Xxf2jX8TAq-4RJEHccdzgZVIO4F5B4MucMZGq8_jMCpiTUsUGDOAMA_AmjxIRHOtO5n6Pt0wofrKoAVhGh2sCTtaQf2Q', + x5c: [ + 'MIIDPzCCAiegAwIBAgIQF+HRVxLHII9IlOoQk6BxcjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBzdHMuZGV2LmNlcnQuY29tMB4XDTE5MDIyMDEwMTA0M1oXDTM5MDIyMDEwMTkyOVowGzEZMBcGA1UEAwwQc3RzLmRldi5jZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrt/hyuKS0RyR6BAcxYTOdekVsep8mxuckIx+DWyo3xkPHbSstQXBo6nzGhChfCjiuODWwMe67QSgjvRCjWJHhb3FMwc1XrruI1hutV2HU3pIL/g4fxY/NuORqHBJG7wWxVunMPvzefSVqlLogr2/hDn4XLrm1HK+OGtAEMjXHGDaXFxkXDCsu/y9WITtADFC6iww1SxY+YJYeE3CdjeVZyXnatfvySjsT6g9Z4tulDkKFq1tehtvSmdf+F8X9o1/EwKvuESRB3HHc4GVSDuBeQeDLnDGRqvP4zAqYk1LFBgzgDAPwJo8SERzrTuZ+j7dMKH6yqAFYRodrAk7WkH9kCAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAtBgNVHREEJjAkghBzdHMuZGV2LmNlcnQuY29tghBzdHMuZGV2LmNlcnQuY29tMB0GA1UdDgQWBBQuyHxWP3je6jGMOmOiY+hz47r36jANBgkqhkiG9w0BAQsFAAOCAQEAKEHG7Ga6nb2XiHXDc69KsIJwbO80+LE8HVJojvITILz3juN6/FmK0HmogjU6cYST7m1MyxsVhQQNwJASZ6haBNuBbNzBXfyyfb4kr62t1oDLNwhctHaHaM4sJSf/xIw+YO+Qf7BtfRAVsbM05+QXIi2LycGrzELiXu7KFM0E1+T8UOZ2Qyv7OlCb/pWkYuDgE4w97ox0MhDpvgluxZLpRanOLUCVGrfFaij7gRAhjYPUY3vAEcD8JcFBz1XijU8ozRO6FaG4qg8/JCe+VgoWsMDj3sKB9g0ob6KCyG9L2bdk99PGgvXDQvMYCpkpZzG3XsxOINPd5p0gc209ZOoxTg==', ], - "alg": "RS256" + alg: 'RS256', } as JsonWebKey; const key2 = { - "kty": "RSA", - "n": "wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU", - "e": "AQAB" + kty: 'RSA', + n: 'wq0vJv4Xl2xSQTN75_N4JeFHlHH80PytypJqyNrhWIp1P9Ur4-5QSiS8BI8PYSh0dQy4NMoj9YMRcyge3y81uCCwxouePiAGc0xPy6QkAOiinvV3KJEMtbppicOvZEzMXb3EqRM-9Twxbp2hhBAPSAhyL79Rwy4JuIQ6imaqL0NIEGv8_BOe_twMPOLGTJhepDO6kDs6O0qlLgPRHQVuKAz3afVby0C2myDLpo5YaI66arU9VXXGQtIp8MhBY9KbsGaYskejSWhSBOcwdtYMEo5rXWGGVnrHiSqq8mm-sVXLQBe5xPFBs4IQ_Gz4nspr05LEEbsHSwFyGq5D77XPxGUPDCq5ZVvON0yBizaHcJ-KA0Lw6uXtOH9-YyVGuaBynkrQEo3pP2iy1uWt-TiQPb8PMsCAdWZP-6R0QKHtjds9HmjIkgFTJSTIeETjNck_bB4ud79gZT-INikjPFTTeyQYk2jqxEJanVe3k0i_1vpskRpknJ7F2vTL45LAQkjWvczjWmHxGA5D4-1msuylXpY8Y4WxnUq6dRTEN29IRVCil9Mfp6JMsquFGTvJO0-Ffl0_suMZZl3uXNt23E9vGreByalWHivYmfpIor5Q5JaFKekRVV-U1KDBaeQQaHp_VqliUKImdUE9-GXNOIaBMjRvfy0nxsRe_q_dD6jc_GU', + e: 'AQAB', } as JsonWebKey; const key3 = { - "kty": "RSA", - "n": "u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw", - "e": "AQAB", - "alg": "RS256", - "kid": "boop", - "use": "sig" + kty: 'RSA', + n: 'u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw', + e: 'AQAB', + alg: 'RS256', + kid: 'boop', + use: 'sig', } as JsonWebKey; const keys: JsonWebKey[] = [key1, key2, key3]; @@ -52,33 +52,35 @@ describe('JwkWindowCryptoService', () => { }); describe('importVerificationKey', () => { - it('returns instance of CryptoKey when valid input is provided',(done) => { - const promises = keys.map((key) => service.importVerificationKey(key, alg)); + it('returns instance of CryptoKey when valid input is provided', (done) => { + const promises = keys.map((key) => service.importVerificationKey(key, alg)); - Promise.all(promises).then(values => { - values.forEach(value => { - expect(value).toBeInstanceOf(CryptoKey); - }) - done(); + Promise.all(promises).then((values) => { + values.forEach((value) => { + expect(value).toBeInstanceOf(CryptoKey); }); - }) + done(); + }); + }); }); describe('verifyKey', () => { it('returns true when valid input is provided', (done) => { - const headerAndPayloadString = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0'; - const signatureString = 'NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ'; - const signature: Uint8Array = base64url.parse(signatureString, { loose: true }); + const headerAndPayloadString = + 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0'; + const signatureString = + 'NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ'; + const signature: Uint8Array = base64url.parse(signatureString, { loose: true }); - service.importVerificationKey(key3, alg) - .then(c => service.verifyKey(alg, c, signature, headerAndPayloadString)) - .then(value => { - expect(value).toEqual(true); - }) - .finally(() =>{ - done(); - }); - } - ); + service + .importVerificationKey(key3, alg) + .then((c) => service.verifyKey(alg, c, signature, headerAndPayloadString)) + .then((value) => { + expect(value).toEqual(true); + }) + .finally(() => { + done(); + }); + }); }); }); diff --git a/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.ts b/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.ts index 41ddeac11..f1a1479c9 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/jwk-window-crypto.service.ts @@ -1,15 +1,23 @@ import { Injectable } from '@angular/core'; -import { CryptoService } from '../utils/crypto/crypto-service'; +import { CryptoService } from '../utils/crypto/crypto.service'; @Injectable() export class JwkWindowCryptoService { constructor(private readonly cryptoService: CryptoService) {} - importVerificationKey(key: JsonWebKey, algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm): Promise { + importVerificationKey( + key: JsonWebKey, + algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm + ): Promise { return this.cryptoService.getCrypto().subtle.importKey('jwk', key, algorithm, false, ['verify']); } - verifyKey(verifyAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams, cryptoKey: CryptoKey, signature: BufferSource, signingInput: string): Promise { - return this.cryptoService.getCrypto().subtle.verify(verifyAlgorithm, cryptoKey, signature, new TextEncoder().encode(signingInput)) + verifyKey( + verifyAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams, + cryptoKey: CryptoKey, + signature: BufferSource, + signingInput: string + ): Promise { + return this.cryptoService.getCrypto().subtle.verify(verifyAlgorithm, cryptoKey, signature, new TextEncoder().encode(signingInput)); } } diff --git a/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.spec.ts index 2ae278fd7..039eb89f9 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed, waitForAsync } from '@angular/core/testing'; -import { CryptoService } from '../utils/crypto/crypto-service'; +import { CryptoService } from '../utils/crypto/crypto.service'; import { JwtWindowCryptoService } from './jwt-window-crypto.service'; describe('JwtWindowCryptoService', () => { @@ -21,16 +21,13 @@ describe('JwtWindowCryptoService', () => { }); describe('generateCodeChallenge', () => { - it( - 'returns good result with correct codeVerifier', - waitForAsync(() => { - const outcome = 'R2TWD45Vtcf_kfAqjuE3LMSRF3JDE5fsFndnn6-a0nQ'; - const observable = service.generateCodeChallenge('44445543344242132145455aaabbdc3b4'); + it('returns good result with correct codeVerifier', waitForAsync(() => { + const outcome = 'R2TWD45Vtcf_kfAqjuE3LMSRF3JDE5fsFndnn6-a0nQ'; + const observable = service.generateCodeChallenge('44445543344242132145455aaabbdc3b4'); - observable.subscribe((value) => { - expect(value).toBe(outcome); - }); - }) - ); + observable.subscribe((value) => { + expect(value).toBe(outcome); + }); + })); }); }); diff --git a/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.ts b/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.ts index af06a5171..705f98965 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/jwt-window-crypto.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { from, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { CryptoService } from '../utils/crypto/crypto-service'; +import { CryptoService } from '../utils/crypto/crypto.service'; @Injectable() export class JwtWindowCryptoService { diff --git a/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts index f41b302cd..4449b082f 100644 --- a/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/validation/token-validation.service.spec.ts @@ -3,7 +3,7 @@ import { of } from 'rxjs'; import { mockClass } from '../../test/auto-mock'; import { JwkExtractor } from '../extractors/jwk.extractor'; import { LoggerService } from '../logging/logger.service'; -import { CryptoService } from '../utils/crypto/crypto-service'; +import { CryptoService } from '../utils/crypto/crypto.service'; import { TokenHelperService } from '../utils/tokenHelper/token-helper.service'; import { JwkWindowCryptoService } from './jwk-window-crypto.service'; import { JwtWindowCryptoService } from './jwt-window-crypto.service'; From bb786e8bbf7006ecf042415eb660544d44398efa Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Tue, 15 Nov 2022 21:03:27 +0100 Subject: [PATCH 5/9] Fix linting --- .../logoff-revoke/logoff-revocation.service.spec.ts | 2 ++ .../src/lib/logoff-revoke/logoff-revocation.service.ts | 1 + .../src/lib/utils/object/object.helper.ts | 1 + .../src/lib/utils/url/url.service.spec.ts | 10 ++++++++++ 4 files changed, 14 insertions(+) diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts index 51dcbdcba..7fac069ab 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.spec.ts @@ -506,6 +506,7 @@ describe('Logout and Revoke Service', () => { ); const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.get('Content-Type')).toBe('application/x-www-form-urlencoded'); }); @@ -552,6 +553,7 @@ describe('Logout and Revoke Service', () => { ); const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTrue(); expect(httpHeaders.get('Content-Type')).toBe('application/x-www-form-urlencoded'); }); diff --git a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts index 6e7fd8818..50b6b0311 100644 --- a/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts +++ b/projects/angular-auth-oidc-client/src/lib/logoff-revoke/logoff-revocation.service.ts @@ -31,6 +31,7 @@ export class LogoffRevocationService { this.loggerService.logDebug(config, 'logoff, remove auth', logoutAuthOptions); const { urlHandler, customParams } = logoutAuthOptions || {}; + this.resetAuthDataService.resetAuthorizationData(config, allConfigs); const endSessionUrl = this.urlService.getEndSessionUrl(config, customParams); diff --git a/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts b/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts index 1bfcaabf4..71a001a54 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/object/object.helper.ts @@ -1,5 +1,6 @@ export function removeNullAndUndefinedValues(obj: any): any { const copy = { ...obj }; + for (const key in obj) { if (obj[key] === undefined || obj[key] === null) { delete copy[key]; diff --git a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts index 77563bda4..2e93ccb75 100644 --- a/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts +++ b/projects/angular-auth-oidc-client/src/lib/utils/url/url.service.spec.ts @@ -1499,6 +1499,7 @@ describe('UrlService Tests', () => { const config = { postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ endSessionEndpoint: 'http://example', @@ -1509,6 +1510,7 @@ describe('UrlService Tests', () => { // Assert const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + expect(value).toEqual(expectValue); }); @@ -1517,6 +1519,7 @@ describe('UrlService Tests', () => { const config = { postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'getIdToken').and.returnValue(null); spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ endSessionEndpoint: 'http://example', @@ -1527,6 +1530,7 @@ describe('UrlService Tests', () => { // Assert const expectValue = 'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + expect(value).toEqual(expectValue); }); @@ -1535,6 +1539,7 @@ describe('UrlService Tests', () => { const config = { postLogoutRedirectUri: 'https://localhost:44386/Unauthorized', } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'getIdToken').and.returnValue('mytoken'); spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ endSessionEndpoint: 'http://example', @@ -1546,6 +1551,7 @@ describe('UrlService Tests', () => { // Assert const expectValue = 'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized¶m=to-add'; + expect(value).toEqual(expectValue); }); @@ -1569,6 +1575,7 @@ describe('UrlService Tests', () => { 'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' + '&id_token_hint=UzI1NiIsImtpZCI6Il' + '&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized'; + expect(value).toEqual(expectValue); }); @@ -1576,6 +1583,7 @@ describe('UrlService Tests', () => { const config = { postLogoutRedirectUri: null, } as OpenIdConfiguration; + spyOn(storagePersistenceService, 'read').withArgs('authWellKnownEndPoints', config).and.returnValue({ endSessionEndpoint: 'http://example', }); @@ -1586,6 +1594,7 @@ describe('UrlService Tests', () => { // Assert const expectValue = 'http://example?id_token_hint=mytoken'; + expect(value).toEqual(expectValue); }); @@ -1616,6 +1625,7 @@ describe('UrlService Tests', () => { // Assert const expectValue = `something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized`; + expect(value).toEqual(expectValue); }); }); From 9968475178dbc3a322de10ed297cfb3614c21451 Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Wed, 16 Nov 2022 07:46:40 +0100 Subject: [PATCH 6/9] Fixed examples --- README.md | 2 +- .../docs/documentation/01-public-api.md | 10 +++--- .../docs/documentation/03-login-logout.md | 31 ++++++++++++------- .../angular-auth-oidc-client/docs/intro.md | 2 +- .../src/lib/auth-options.ts | 1 - .../src/app/home/home.component.ts | 2 +- .../app/navigation/navigation.component.ts | 2 +- .../src/app/app.component.ts | 2 +- .../app/navigation/navigation.component.ts | 2 +- .../src/app/nav-menu/nav-menu.component.ts | 2 +- .../src/app/app.component.ts | 2 +- .../src/app/nav-menu/nav-menu.component.ts | 2 +- .../src/app/home/home.component.ts | 2 +- .../src/app/lazy/lazy.component.ts | 2 +- .../src/app/home/home.component.ts | 2 +- .../src/app/app.component.ts | 2 +- .../src/app/home/home.component.ts | 2 +- .../src/app/app.component.ts | 2 +- .../app/navigation/navigation.component.ts | 2 +- .../src/app/home/home.component.ts | 2 +- 20 files changed, 43 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 63565e934..79977fd93 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ export class AppComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } ``` diff --git a/docs/site/angular-auth-oidc-client/docs/documentation/01-public-api.md b/docs/site/angular-auth-oidc-client/docs/documentation/01-public-api.md index f8bb8219a..a43d72658 100644 --- a/docs/site/angular-auth-oidc-client/docs/documentation/01-public-api.md +++ b/docs/site/angular-auth-oidc-client/docs/documentation/01-public-api.md @@ -601,14 +601,16 @@ const authOptions = { this.oidcSecurityService.logoffAndRevokeTokens('configId', authOptions).subscribe(/* ... */); ``` -## logoff(configId?: string, authOptions?: AuthOptions) +## logoff(configId?: string, logoutAuthOptions?: LogoutAuthOptions) -This method logs out on the server and the local client. If the server state has changed, check session, then only a local logout. The method takes a `configId` and `authOptions` as parameter. If you are running with multiple configs and pass the `configId` the passed config is taken. If you are running with multiple configs and do not pass the `configId` the first config is taken. If you are running with a single config this config is taken. +This method logs out on the server and the local client. If the server state has changed, check session, then only a local logout. The method takes a `configId` and `logoutAuthOptions` as parameter. If you are running with multiple configs and pass the `configId` the passed config is taken. If you are running with multiple configs and do not pass the `configId` the first config is taken. If you are running with a single config this config is taken. + +The method returns an `Observable`. Examples: ```ts -this.oidcSecurityService.logoff(); +this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); ``` ```ts @@ -621,7 +623,7 @@ const authOptions = { }, }; -this.oidcSecurityService.logoff('configId', authOptions); +this.oidcSecurityService.logoff('configId', authOptions).subscribe((result) => console.log(result)); ``` ## logoffLocal(configId?: string) diff --git a/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md b/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md index fdf0e1a9d..a39e8b4cf 100644 --- a/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md +++ b/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md @@ -26,7 +26,7 @@ In case you have multiple configs you can pass the `configId` parameter as the f ```ts login() { - this.oidcSecurityService.authorizeWithPopUp('configId') + this.oidcSecurityService.authorize('configId') .subscribe(({ isAuthenticated, userData, idToken, accessToken, errorMessage }) => { // ... }); @@ -55,7 +55,7 @@ login() { const configIdOrNull = // ... - this.oidcSecurityService.authorizeWithPopUp(configIdOrNull, authOptions) + this.oidcSecurityService.authorize(configIdOrNull, authOptions) .subscribe(({ isAuthenticated, userData, idToken, accessToken, errorMessage }) => { // ... }); @@ -73,13 +73,14 @@ This allows you to have the provider's consent prompt display in a popup window ```ts loginWithPopup() { - this.oidcSecurityService.authorizeWithPopUp().subscribe(({ isAuthenticated, userData, accessToken, errorMessage }) => { + this.oidcSecurityService.authorizeWithPopUp() + .subscribe(({ isAuthenticated, userData, accessToken, errorMessage }) => { /* use data */ }); } ``` -### AuthOptions & PopupOptions +### PopupOptions You can pass options to control the dimension of the popup with the `PopupOptions` interface as a second parameter. @@ -138,7 +139,7 @@ A simplified page (instead of the application url) can be used. Here's an exampl ### Popup Sample -[app.component.ts](../../../../../projects/sample-code-flow-popup/src/app/app.component.ts) +[app.component.ts](../../../../../projects/sample-code-flow-popup/src/app/) ## Logout @@ -146,7 +147,7 @@ The `logoff()` method sends an end session request to the OIDC server, if it is ```ts logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } ``` @@ -163,13 +164,21 @@ logout() { } ``` -### AuthOptions Parameter +### LogoutAuthOptions -You can pass an `authOptions` parameter if you want to control the behavior more. +You can pass in LogoutAuthOptions following optional parameters: + +- `urlHandler` - to manipulate the behavior of the logout with a custom `urlHandler` +- `customParams` - to send custom parameters to OIDC Provider +- `logoffMethod` - Which can be `GET` or `POST`. `GET` is default here. + +According to the [OIDC Standard](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) only the customParams `state`, `logout_hint` and `ui_locales` are configurable. Other values are being created, being read from storage or taken from your config. + +You can pass an `logoutAuthOptions` parameter if you want to control the behavior more. ```ts logout() { - const authOptions = { + const logoutAuthOptions = { customParams: { some: 'params', }, @@ -178,7 +187,7 @@ logout() { }, }; - this.oidcSecurityService.logoff('configId', authOptions); + this.oidcSecurityService.logoff('configId', logoutAuthOptions); } ``` @@ -193,7 +202,7 @@ logoffAndRevokeTokens() { } ``` -The method also takes `configId` and `authOptions` parameters if needed. +The method also takes `configId` and `logoutAuthOptions` parameters if needed. ### `logoffLocal()` diff --git a/docs/site/angular-auth-oidc-client/docs/intro.md b/docs/site/angular-auth-oidc-client/docs/intro.md index 70e216f5a..824686bf9 100644 --- a/docs/site/angular-auth-oidc-client/docs/intro.md +++ b/docs/site/angular-auth-oidc-client/docs/intro.md @@ -89,7 +89,7 @@ export class AppComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } ``` diff --git a/projects/angular-auth-oidc-client/src/lib/auth-options.ts b/projects/angular-auth-oidc-client/src/lib/auth-options.ts index b6969f86c..10da4c8c8 100644 --- a/projects/angular-auth-oidc-client/src/lib/auth-options.ts +++ b/projects/angular-auth-oidc-client/src/lib/auth-options.ts @@ -8,6 +8,5 @@ export interface AuthOptions { export interface LogoutAuthOptions { customParams?: { [key: string]: string | number | boolean }; urlHandler?(url: string): any; - /** overrides redirectUrl from configuration */ logoffMethod?: 'GET' | 'POST'; } diff --git a/projects/sample-code-flow-auth0/src/app/home/home.component.ts b/projects/sample-code-flow-auth0/src/app/home/home.component.ts index f49068665..bdc904a5e 100644 --- a/projects/sample-code-flow-auth0/src/app/home/home.component.ts +++ b/projects/sample-code-flow-auth0/src/app/home/home.component.ts @@ -34,7 +34,7 @@ export class HomeComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } logoffAndRevokeTokens() { diff --git a/projects/sample-code-flow-auto-login-all-routes/src/app/navigation/navigation.component.ts b/projects/sample-code-flow-auto-login-all-routes/src/app/navigation/navigation.component.ts index 5e05394e6..9d9018190 100644 --- a/projects/sample-code-flow-auto-login-all-routes/src/app/navigation/navigation.component.ts +++ b/projects/sample-code-flow-auto-login-all-routes/src/app/navigation/navigation.component.ts @@ -28,6 +28,6 @@ export class NavigationComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-code-flow-auto-login/src/app/app.component.ts b/projects/sample-code-flow-auto-login/src/app/app.component.ts index 5529e23ef..1c255b18a 100644 --- a/projects/sample-code-flow-auto-login/src/app/app.component.ts +++ b/projects/sample-code-flow-auto-login/src/app/app.component.ts @@ -28,6 +28,6 @@ export class AppComponent implements OnInit { logout() { console.log('start logoff'); - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts index 5e05394e6..9d9018190 100644 --- a/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts +++ b/projects/sample-code-flow-auto-login/src/app/navigation/navigation.component.ts @@ -28,6 +28,6 @@ export class NavigationComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-code-flow-azure-b2c/src/app/nav-menu/nav-menu.component.ts b/projects/sample-code-flow-azure-b2c/src/app/nav-menu/nav-menu.component.ts index 6f382ba2d..976eb0c31 100644 --- a/projects/sample-code-flow-azure-b2c/src/app/nav-menu/nav-menu.component.ts +++ b/projects/sample-code-flow-azure-b2c/src/app/nav-menu/nav-menu.component.ts @@ -29,7 +29,7 @@ export class NavMenuComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } collapse() { diff --git a/projects/sample-code-flow-azuread/src/app/app.component.ts b/projects/sample-code-flow-azuread/src/app/app.component.ts index 2a083f486..b5da24b1c 100644 --- a/projects/sample-code-flow-azuread/src/app/app.component.ts +++ b/projects/sample-code-flow-azuread/src/app/app.component.ts @@ -24,6 +24,6 @@ export class AppComponent implements OnInit, OnDestroy { logout() { console.log('start logoff'); - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-code-flow-azuread/src/app/nav-menu/nav-menu.component.ts b/projects/sample-code-flow-azuread/src/app/nav-menu/nav-menu.component.ts index 66c2f0be5..4bb5bd983 100644 --- a/projects/sample-code-flow-azuread/src/app/nav-menu/nav-menu.component.ts +++ b/projects/sample-code-flow-azuread/src/app/nav-menu/nav-menu.component.ts @@ -30,7 +30,7 @@ export class NavMenuComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } collapse() { this.isExpanded = false; diff --git a/projects/sample-code-flow-http-config/src/app/home/home.component.ts b/projects/sample-code-flow-http-config/src/app/home/home.component.ts index c390eecb8..915e13c24 100644 --- a/projects/sample-code-flow-http-config/src/app/home/home.component.ts +++ b/projects/sample-code-flow-http-config/src/app/home/home.component.ts @@ -37,7 +37,7 @@ export class HomeComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } logoffLocal() { diff --git a/projects/sample-code-flow-lazy-loaded/src/app/lazy/lazy.component.ts b/projects/sample-code-flow-lazy-loaded/src/app/lazy/lazy.component.ts index cbfff73ed..3e42250ac 100644 --- a/projects/sample-code-flow-lazy-loaded/src/app/lazy/lazy.component.ts +++ b/projects/sample-code-flow-lazy-loaded/src/app/lazy/lazy.component.ts @@ -23,6 +23,6 @@ export class LazyComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-code-flow-par/src/app/home/home.component.ts b/projects/sample-code-flow-par/src/app/home/home.component.ts index 983c02c0d..0262ba057 100644 --- a/projects/sample-code-flow-par/src/app/home/home.component.ts +++ b/projects/sample-code-flow-par/src/app/home/home.component.ts @@ -41,7 +41,7 @@ export class HomeComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } logoffAndRevokeTokens() { diff --git a/projects/sample-code-flow-popup/src/app/app.component.ts b/projects/sample-code-flow-popup/src/app/app.component.ts index 41e637d5a..17b307954 100644 --- a/projects/sample-code-flow-popup/src/app/app.component.ts +++ b/projects/sample-code-flow-popup/src/app/app.component.ts @@ -54,6 +54,6 @@ export class AppComponent { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-code-flow-refresh-tokens/src/app/home/home.component.ts b/projects/sample-code-flow-refresh-tokens/src/app/home/home.component.ts index 1da671d46..4ff03ae10 100644 --- a/projects/sample-code-flow-refresh-tokens/src/app/home/home.component.ts +++ b/projects/sample-code-flow-refresh-tokens/src/app/home/home.component.ts @@ -33,7 +33,7 @@ export class HomeComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } logoffAndRevokeTokens() { diff --git a/projects/sample-implicit-flow-google/src/app/app.component.ts b/projects/sample-implicit-flow-google/src/app/app.component.ts index c886706ce..b13fc3895 100644 --- a/projects/sample-implicit-flow-google/src/app/app.component.ts +++ b/projects/sample-implicit-flow-google/src/app/app.component.ts @@ -40,7 +40,7 @@ export class AppComponent implements OnInit, OnDestroy { logout() { console.log('start logoff'); - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } private navigateToStoredEndpoint() { diff --git a/projects/sample-implicit-flow-google/src/app/navigation/navigation.component.ts b/projects/sample-implicit-flow-google/src/app/navigation/navigation.component.ts index eb278df80..467bb1061 100644 --- a/projects/sample-implicit-flow-google/src/app/navigation/navigation.component.ts +++ b/projects/sample-implicit-flow-google/src/app/navigation/navigation.component.ts @@ -27,6 +27,6 @@ export class NavigationComponent implements OnInit { } logout() { - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } diff --git a/projects/sample-implicit-flow-silent-renew/src/app/home/home.component.ts b/projects/sample-implicit-flow-silent-renew/src/app/home/home.component.ts index d62207cdf..6aa83d12f 100644 --- a/projects/sample-implicit-flow-silent-renew/src/app/home/home.component.ts +++ b/projects/sample-implicit-flow-silent-renew/src/app/home/home.component.ts @@ -42,6 +42,6 @@ export class HomeComponent implements OnInit { logout() { console.log('start logoff'); - this.oidcSecurityService.logoff(); + this.oidcSecurityService.logoff().subscribe((result) => console.log(result)); } } From be918e35aac1ad5ee7b49407cf210146c82a53aa Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Wed, 16 Nov 2022 08:06:53 +0100 Subject: [PATCH 7/9] Fixed changelog and migration --- CHANGELOG.md | 3 +- .../docs/migrations/v14-to-v15.md | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705c527bc..8d823b4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### 2022-11-XX 15.0.0 -- Support for refresh without id_token, run silent renew using only the access token +- Support for refresh without id_token - [PR](https://github.com/damienbod/angular-auth-oidc-client/pull/1571) - Support refresh tokens without returning an id_token in the refresh - run silent renew using only the access token @@ -10,6 +10,7 @@ - add support to disable id_token validation completely, not recommended - Renamed `enableIdTokenExpiredValidationInRenew` to `triggerRefreshWhenIdTokenExpired` - Added `disableIdTokenValidation` parameter in config +- `logoff()` possible now with `POST` request Docs: [https://angular-auth-oidc-client.com/docs/documentation/silent-renew](https://github.com/damienbod/angular-auth-oidc-client/pull/1541) diff --git a/docs/site/angular-auth-oidc-client/docs/migrations/v14-to-v15.md b/docs/site/angular-auth-oidc-client/docs/migrations/v14-to-v15.md index 9613c6938..633cc9ef9 100644 --- a/docs/site/angular-auth-oidc-client/docs/migrations/v14-to-v15.md +++ b/docs/site/angular-auth-oidc-client/docs/migrations/v14-to-v15.md @@ -4,10 +4,11 @@ sidebar_position: 5 # Version 14 to 15 -## TL;DR +## TL;DR: Breaking Changes - `enableIdTokenExpiredValidationInRenew` was renamed to `triggerRefreshWhenIdTokenExpired` - `isLoading$` property was removed -> use Public Event Service instead +- `logoff()` method now returns an `Observable` and accepts a `logoutOptions` parameter All changes are described below. @@ -15,6 +16,40 @@ All changes are described below. The configuration `enableIdTokenExpiredValidationInRenew` was renamed to `triggerRefreshWhenIdTokenExpired` to match its function. The `triggerRefreshWhenIdTokenExpired` parameter can be set to `false` and the renew process with not be triggered by an expired id_token. +Old: + +``` +const config = { + //... + enableIdTokenExpiredValidationInRenew: true|false +} +``` + +New: + +``` +const config = { + //... + triggerRefreshWhenIdTokenExpired: true|false +} +``` + ## `isLoading$` property was removed -> use Public Event Service instead -The `isLoading$` was marked as deprecated and is removed now. If you want to know when `checkAuth` is finished, use the [PublicEventsService](https://www.angular-auth-oidc-client.com/docs/documentation/public-events) and listen to the Events [CheckingAuth, CheckinfAuthFinished](https://github.com/damienbod/angular-auth-oidc-client/blob/main/projects/angular-auth-oidc-client/src/lib/public-events/event-types.ts#L7-L8) +The `isLoading$` was marked as deprecated and is removed now. If you want to know when `checkAuth` is finished, use the [PublicEventsService](https://www.angular-auth-oidc-client.com/docs/documentation/public-events) and listen to the Events [CheckingAuth, CheckingAuthFinished](https://github.com/damienbod/angular-auth-oidc-client/blob/main/projects/angular-auth-oidc-client/src/lib/public-events/event-types.ts#L7-L8) + +## `logoff()` method now returns an `Observable` + +According to the standard we enabled logging out via `POST` request. For this, the API needed to change. + +Old: + +``` +this.oidcSecurityService.logoff(); +``` + +New: + +``` +this.oidcSecurityService.logoff(),subscribe(/*...*/); +``` From 91702f7c56b7953d0c9ec7c8371db30ed3d9acb7 Mon Sep 17 00:00:00 2001 From: FabianGosebrink Date: Wed, 16 Nov 2022 17:59:24 +0100 Subject: [PATCH 8/9] Fixed docs and changelog --- CHANGELOG.md | 6 ++++-- .../docs/documentation/03-login-logout.md | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d823b4d2..1aebfd7f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,17 @@ ### 2022-11-XX 15.0.0 -- Support for refresh without id_token - - [PR](https://github.com/damienbod/angular-auth-oidc-client/pull/1571) - Support refresh tokens without returning an id_token in the refresh - run silent renew using only the access token - id_token only has to be valid on the first authentication - add support to disable id_token validation completely, not recommended - Renamed `enableIdTokenExpiredValidationInRenew` to `triggerRefreshWhenIdTokenExpired` - Added `disableIdTokenValidation` parameter in config + - [PR](https://github.com/damienbod/angular-auth-oidc-client/pull/1571) - `logoff()` possible now with `POST` request + - [PR](https://github.com/damienbod/angular-auth-oidc-client/pull/1582) +- removed deprecated `isLoading$` property + - [PR](https://github.com/damienbod/angular-auth-oidc-client/pull/1580) Docs: [https://angular-auth-oidc-client.com/docs/documentation/silent-renew](https://github.com/damienbod/angular-auth-oidc-client/pull/1541) diff --git a/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md b/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md index a39e8b4cf..c9e207151 100644 --- a/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md +++ b/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md @@ -151,7 +151,9 @@ logout() { } ``` -### `ConfigId` Parameter +### Parameters + +#### configId `logoff()` also accepts a `configId` paramater to select a specific config: @@ -164,7 +166,7 @@ logout() { } ``` -### LogoutAuthOptions +#### LogoutAuthOptions You can pass in LogoutAuthOptions following optional parameters: From d5ad10a31e22882f6ea40e6060771ac248e5993b Mon Sep 17 00:00:00 2001 From: damienbod Date: Fri, 18 Nov 2022 10:04:13 +0100 Subject: [PATCH 9/9] Update docs --- .../docs/documentation/03-login-logout.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md b/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md index c9e207151..adc16e3f9 100644 --- a/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md +++ b/docs/site/angular-auth-oidc-client/docs/documentation/03-login-logout.md @@ -193,6 +193,20 @@ logout() { } ``` +If you prefer to send a POST logout request: + +``` +logout() { + // logoffMethod` - Which can be `GET` or `POST + const logoutAuthOptions: LogoutAuthOptions = { + logoffMethod: 'POST', + }; + + this.oidcSecurityService.logoff('', logoutAuthOptions) + .subscribe((result) => console.log(result)); +} +``` + ### `logoffAndRevokeTokens()` The `logoffAndRevokeTokens()` method revokes the access token and the refresh token if using a refresh flow, and then logoff like above.