From c9d0066d64eb82dc738c92f2b3b3aaa2a9180dd5 Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Fri, 31 Jan 2025 05:57:51 +0800 Subject: [PATCH] fix: fix all biome --- biome.jsonc | 9 +- scripts/code-transform.spec.ts | 51 ++- scripts/code-transform.ts | 137 ++++-- src/api/data.service.ts | 8 +- src/api/http-base.service.ts | 4 +- src/auth-state/auth-state.service.spec.ts | 3 +- src/auth-state/auth-state.service.ts | 3 +- src/auth-state/auth-state.ts | 2 +- src/auth-state/check-auth.service.spec.ts | 413 ++++++++++-------- src/auth-state/check-auth.service.ts | 2 +- src/auth.module.spec.ts | 1 - .../auto-login-partial-routes.guard.spec.ts | 2 +- src/auto-login/auto-login.service.spec.ts | 3 - src/auto-login/auto-login.service.ts | 2 +- .../code-flow-callback.service.spec.ts | 90 ++-- .../implicit-flow-callback.service.spec.ts | 92 ++-- src/callback/interval.service.spec.ts | 11 +- ...resh-session-refresh-token.service.spec.ts | 40 +- src/callback/refresh-session.service.spec.ts | 52 +-- .../auth-well-known-data.service.spec.ts | 74 ++-- .../auth-well-known-data.service.ts | 8 +- .../auth-well-known.service.spec.ts | 42 +- .../auth-well-known.service.ts | 8 +- src/config/config.service.spec.ts | 4 +- src/config/default-config.ts | 2 +- src/config/loader/config-loader.spec.ts | 11 +- .../validation/config-validation.service.ts | 22 +- src/config/validation/rule.ts | 2 +- .../validation/rules/ensure-authority.rule.ts | 4 +- .../validation/rules/ensure-clientId.rule.ts | 4 +- .../ensure-no-duplicated-configs.rule.ts | 4 +- .../rules/ensure-redirect-url.rule.ts | 4 +- ...lentRenewUrl-with-no-refreshtokens.rule.ts | 4 +- ...se-offline-scope-with-silent-renew.rule.ts | 4 +- src/extractors/jwk.extractor.spec.ts | 4 - src/extractors/jwk.extractor.ts | 12 +- src/flows/callback-context.ts | 4 +- ...code-flow-callback-handler.service.spec.ts | 191 ++++---- .../code-flow-callback-handler.service.ts | 6 +- ...-jwt-keys-callback-handler.service.spec.ts | 363 ++++++++------- ...story-jwt-keys-callback-handler.service.ts | 6 +- ...icit-flow-callback-handler.service.spec.ts | 29 +- ...h-session-callback-handler.service.spec.ts | 25 +- ...efresh-session-callback-handler.service.ts | 21 +- ...esh-token-callback-handler.service.spec.ts | 121 +++-- .../refresh-token-callback-handler.service.ts | 6 +- ...alidation-callback-handler.service.spec.ts | 82 ++-- ...ate-validation-callback-handler.service.ts | 27 +- .../user-callback-handler.service.spec.ts | 161 +++---- .../user-callback-handler.service.ts | 24 +- src/flows/flows-data.service.spec.ts | 68 +-- src/flows/flows.service.spec.ts | 85 ++-- src/flows/random/random.service.spec.ts | 4 - src/flows/random/random.service.ts | 6 +- src/flows/reset-auth-data.service.spec.ts | 3 - src/flows/signin-key-data.service.spec.ts | 143 +++--- src/flows/signin-key-data.service.ts | 8 +- src/iframe/check-session.service.spec.ts | 132 +++--- src/iframe/existing-iframe.service.ts | 6 +- src/iframe/silent-renew.service.spec.ts | 82 ++-- src/iframe/silent-renew.service.ts | 2 +- src/interceptor/auth.interceptor.spec.ts | 49 ++- src/interceptor/auth.interceptor.ts | 4 +- .../closest-matching-route.service.spec.ts | 4 - .../closest-matching-route.service.ts | 2 +- src/logging/console-logger.service.ts | 5 +- src/logging/log-level.ts | 8 +- src/logging/logger.service.spec.ts | 3 - src/login/login.service.spec.ts | 45 +- src/login/par/par-login.service.spec.ts | 169 +++---- src/login/par/par-login.service.ts | 65 +-- src/login/par/par.service.spec.ts | 109 +++-- src/login/par/par.service.ts | 10 +- src/login/popup/popup-login.service.spec.ts | 14 +- src/login/popup/popup.service.spec.ts | 36 +- src/login/popup/popup.service.ts | 12 +- .../response-type-validation.service.spec.ts | 3 - .../response-type-validation.service.ts | 2 +- .../standard/standard-login.service.spec.ts | 37 +- src/login/standard/standard-login.service.ts | 58 +-- .../logoff-revocation.service.spec.ts | 242 +++++----- .../logoff-revocation.service.ts | 25 +- src/public-events/event-types.ts | 1 - src/public-events/notification.ts | 2 +- .../public-events.service.spec.ts | 40 +- src/public-events/public-events.service.ts | 6 +- src/router/index.ts | 8 +- src/storage/browser-storage.service.spec.ts | 14 +- src/storage/browser-storage.service.ts | 6 +- .../default-localstorage.service.spec.ts | 3 - src/storage/default-localstorage.service.ts | 10 +- .../default-sessionstorage.service.spec.ts | 3 - src/storage/default-sessionstorage.service.ts | 10 +- .../storage-persistence.service.spec.ts | 3 - src/testing/create-retriable-stream.helper.ts | 3 +- src/user-data/user-service.spec.ts | 3 +- src/utils/crypto/crypto.service.spec.ts | 7 - src/utils/equality/equality.service.spec.ts | 4 - src/utils/equality/equality.service.ts | 7 +- .../flowHelper/flow-helper.service.spec.ts | 3 - .../platform-provider.spec.ts | 1 - .../platform-provider/platform.provider.ts | 9 +- src/utils/redirect/redirect.service.spec.ts | 6 +- .../tokenHelper/token-helper.service.spec.ts | 6 +- src/utils/tokenHelper/token-helper.service.ts | 10 +- src/utils/url/uri-encoder.ts | 2 +- src/utils/url/url.service.ts | 53 ++- .../jwt-window-crypto.service.spec.ts | 7 +- src/validation/jwt-window-crypto.service.ts | 2 +- src/validation/state-validation-result.ts | 10 + .../state-validation.service.spec.ts | 310 +++++++------ src/validation/state-validation.service.ts | 29 +- src/validation/token-validation.helper.ts | 32 +- src/validation/token-validation.service.ts | 68 ++- 114 files changed, 2255 insertions(+), 2068 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index b6d086e..1691b58 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -4,19 +4,24 @@ "linter": { "rules": { "style": { - "noNonNullAssertion": "off" + "noNonNullAssertion": "off", + "noParameterAssign": "off", + "useFilenamingConvention": "warn" }, "suspicious": { "noExplicitAny": "off" }, "complexity": { - "noForEach": "info" + "noForEach": "off" }, "correctness": { "noUnusedImports": { "fix": "none", "level": "warn" } + }, + "nursery": { + "noEnum": "off" } } }, diff --git a/scripts/code-transform.spec.ts b/scripts/code-transform.spec.ts index 0d041a9..aac88fc 100644 --- a/scripts/code-transform.spec.ts +++ b/scripts/code-transform.spec.ts @@ -3,8 +3,8 @@ import { describe, it } from 'node:test'; import { Biome, Distribution } from '@biomejs/js-api'; import { rewriteObservableSubscribeToLastValueFrom } from './code-transform'; -describe('writeAllSpecObservableSubscribeToLastValueFrom', () => { - it('should transform valid string', async () => { +describe('rewriteSpecObservableSubscribeToLastValueFrom', () => { + it('should transform simple example valid string', async () => { const actual = await rewriteObservableSubscribeToLastValueFrom( 'index.ts', `refreshSessionIframeService @@ -32,4 +32,51 @@ describe('writeAllSpecObservableSubscribeToLastValueFrom', () => { biome.formatContent(expect, { filePath: 'index.ts' }).content ); }); + + it('should rewrite complex exmaple to valid string', async () => { + const actual = await rewriteObservableSubscribeToLastValueFrom( + 'index.ts', + `codeFlowCallbackService + .authenticatedCallbackWithCode('some-url4', config, [config]) + .subscribe({ + error: (err: any) => { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(resetCodeFlowInProgressSpy).toHaveBeenCalled(); + expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + }, + next: (abc) => { + expect(abc).toBeTruthy(); + }, + complete () { + expect.fail('complete') + } + });` + ); + + const expect = ` + try { + const abc = await lastValueFrom(codeFlowCallbackService.authenticatedCallbackWithCode('some-url4', config, [config])); + expect(abc).toBeTruthy(); + } catch (err: any) { + if (err instanceof EmptyError) { + expect.fail('complete') + } else { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(resetCodeFlowInProgressSpy).toHaveBeenCalled(); + expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } + } + `; + + const biome = await Biome.create({ + distribution: Distribution.NODE, + }); + + assert.equal( + biome.formatContent(actual, { filePath: 'index.ts' }).content, + biome.formatContent(expect, { filePath: 'index.ts' }).content + ); + }); }); diff --git a/scripts/code-transform.ts b/scripts/code-transform.ts index 1adccd6..0f5a357 100644 --- a/scripts/code-transform.ts +++ b/scripts/code-transform.ts @@ -1,11 +1,18 @@ import assert from 'node:assert/strict'; import fsp from 'node:fs/promises'; -import { type MagicString, type Statement, parseSync } from 'oxc-parser'; -import { type Node, walk } from 'oxc-walker'; +import { + type ArrowFunctionExpression, + // biome-ignore lint/suspicious/noShadowRestrictedNames: + type Function, + type MagicString, + type Statement, + parseSync, +} from 'oxc-parser'; +import { walk } from 'oxc-walker'; function sourceTextFromNode( context: { magicString?: MagicString }, - node: Node + node: { start: number; end: number } ): string { const magicString = context.magicString; assert(magicString, 'magicString should be defined'); @@ -33,53 +40,101 @@ export async function rewriteObservableSubscribeToLastValueFrom( child.type === 'ExpressionStatement' && child.expression.type === 'CallExpression' && child.expression.callee.type === 'StaticMemberExpression' && - child.expression.callee.property.name === 'subscribe' && - child.expression.arguments.length === 0 + child.expression.callee.property.name === 'subscribe' ) { - const newContent = `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`; + let next: ArrowFunctionExpression | Function | undefined; + let error: ArrowFunctionExpression | Function | undefined; + let complete: ArrowFunctionExpression | Function | undefined; - const newStatements = parseSync('index.ts', newContent).program - .body as any[]; + if (child.expression.arguments[0]?.type === 'ObjectExpression') { + const obj = child.expression.arguments[0]; + for (const prop of obj.properties) { + if ( + prop.type === 'ObjectProperty' && + prop.key.type === 'Identifier' && + (prop.value.type === 'FunctionExpression' || + prop.value.type === 'ArrowFunctionExpression') + ) { + if (prop.key.name === 'next') { + next = prop.value; + } else if (prop.key.name === 'error') { + error = prop.value; + } else if (prop.key.name === 'complete') { + complete = prop.value; + } + } + } + } else if ( + child.expression.arguments.find( + (arg) => + arg.type === 'FunctionExpression' || + arg.type === 'ArrowFunctionExpression' + ) + ) { + const args: Array< + Function | ArrowFunctionExpression | undefined + > = child.expression.arguments.map((arg) => + arg.type === 'FunctionExpression' || + arg.type === 'ArrowFunctionExpression' + ? arg + : undefined + ); + next = args[0]; + error = args[1]; + complete = args[2]; + } + let newContent = `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`; - magicString.remove(child.start, child.end); - magicString.appendRight(child.start, newContent); + if (next) { + const nextParam = + next?.params?.items?.[0]?.type === 'FormalParameter' + ? sourceTextFromNode(context, next.params.items[0]) + : undefined; - newChildren.push(...newStatements); - } else if ( - child.type === 'ExpressionStatement' && - child.expression.type === 'CallExpression' && - child.expression.callee.type === 'StaticMemberExpression' && - child.expression.callee.property.name === 'subscribe' && - child.expression.arguments[0]?.type === 'ArrowFunctionExpression' && - child.expression.arguments[0].body.type === 'FunctionBody' - ) { - const awaited = - child.expression.arguments[0].params.kind === - 'ArrowFormalParameters' && - child.expression.arguments[0].params.items[0]?.type === - 'FormalParameter' && - child.expression.arguments[0].params.items[0].pattern.type === - 'Identifier' - ? child.expression.arguments[0].params.items[0].pattern.name - : undefined; - const newContent = - (awaited - ? `const ${awaited} = await lastValueFrom(${sourceTextFromNode( - context, - child.expression.callee.object - )});\n` - : `await lastValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});\n`) + - child.expression.arguments[0].body.statements + if (nextParam) { + newContent = `const ${nextParam} = ${newContent}`; + } + newContent += (next.body?.statements || []) .map((s) => sourceTextFromNode(context, s)) - .join(';\n'); + .join('\n'); + } - const newStatements = parseSync('index.ts', newContent).program - .body as any[]; + if (error || complete) { + const errorParam = + error?.params?.items?.[0]?.type === 'FormalParameter' && + error.params.items[0].pattern.type === 'Identifier' + ? sourceTextFromNode(context, error.params.items[0]) + : 'err'; + const errorParamName = + error?.params?.items?.[0]?.type === 'FormalParameter' && + error.params.items[0].pattern.type === 'Identifier' + ? error.params.items[0].pattern.name + : 'err'; + + let errorBody = ''; + if (error) { + errorBody += (error.body?.statements || []) + .map((s) => sourceTextFromNode(context, s)) + .join('\n'); + } + if (complete) { + const completBody = `if (${errorParamName} instanceof EmptyError) { ${(complete.body?.statements || []).map((s) => sourceTextFromNode(context, s)).join('\n')}}`; + if (errorBody) { + errorBody = `${completBody} else { ${errorBody} }`; + } else { + errorBody = completBody; + } + } + + newContent = `try { ${newContent} } catch (${errorParam}) { ${errorBody} }`; + } + + const newNodes = parseSync('index.html', newContent).program.body; magicString.remove(child.start, child.end); - magicString.appendRight(child.start, newContent); + magicString.appendLeft(child.start, newContent); - newChildren.push(...newStatements); + newChildren.push(...newNodes); } else { newChildren.push(child as any); } diff --git a/src/api/data.service.ts b/src/api/data.service.ts index 2a034b6..6e92abc 100644 --- a/src/api/data.service.ts +++ b/src/api/data.service.ts @@ -1,7 +1,7 @@ import { HttpHeaders, HttpParams } from '@ngify/http'; import { Injectable, inject } from 'injection-js'; -import { Observable } from 'rxjs'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { Observable } from 'rxjs'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { HttpBaseService } from './http-base.service'; const NGSW_CUSTOM_PARAM = 'ngsw-bypass'; @@ -41,10 +41,10 @@ export class DataService { headers = headers.set('Accept', 'application/json'); - if (!!token) { + if (token) { headers = headers.set( 'Authorization', - 'Bearer ' + decodeURIComponent(token) + `Bearer ${decodeURIComponent(token)}` ); } diff --git a/src/api/http-base.service.ts b/src/api/http-base.service.ts index 9673397..e0d5f2e 100644 --- a/src/api/http-base.service.ts +++ b/src/api/http-base.service.ts @@ -1,11 +1,9 @@ import { HttpClient } from '@ngify/http'; import { Injectable, inject } from 'injection-js'; -import { Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; @Injectable() export class HttpBaseService { - constructor() {} - private readonly http = inject(HttpClient); get(url: string, params?: { [key: string]: unknown }): Observable { diff --git a/src/auth-state/auth-state.service.spec.ts b/src/auth-state/auth-state.service.spec.ts index 5379e1e..84826d2 100644 --- a/src/auth-state/auth-state.service.spec.ts +++ b/src/auth-state/auth-state.service.spec.ts @@ -38,7 +38,7 @@ describe('Auth State Service', () => { expect(authStateService).toBeTruthy(); }); - it('public authorize$ is observable$', () => { + it('authorize$ is observable$', () => { expect(authStateService.authenticated$).toBeInstanceOf(Observable); }); @@ -269,6 +269,7 @@ describe('Auth State Service', () => { it('does not crash and store accessToken when authResult is null', () => { const spy = vi.spyOn(storagePersistenceService, 'write'); + // biome-ignore lint/suspicious/noEvolvingTypes: const authResult = null; authStateService.setAuthorizationData( diff --git a/src/auth-state/auth-state.service.ts b/src/auth-state/auth-state.service.ts index baf74a7..27489b5 100644 --- a/src/auth-state/auth-state.service.ts +++ b/src/auth-state/auth-state.service.ts @@ -257,9 +257,8 @@ export class AuthStateService { private decodeURIComponentSafely(token: string): string { if (token) { return decodeURIComponent(token); - } else { - return ''; } + return ''; } private persistAccessTokenExpirationTime( diff --git a/src/auth-state/auth-state.ts b/src/auth-state/auth-state.ts index a8e2191..6828f43 100644 --- a/src/auth-state/auth-state.ts +++ b/src/auth-state/auth-state.ts @@ -1,4 +1,4 @@ -import { ValidationResult } from '../validation/validation-result'; +import type { ValidationResult } from '../validation/validation-result'; export interface AuthStateResult { isAuthenticated: boolean; diff --git a/src/auth-state/check-auth.service.spec.ts b/src/auth-state/check-auth.service.spec.ts index adac39f..ec3ec73 100644 --- a/src/auth-state/check-auth.service.spec.ts +++ b/src/auth-state/check-auth.service.spec.ts @@ -135,12 +135,14 @@ describe('CheckAuthService', () => { ); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - checkAuthService.checkAuth(allConfigs[0]!, allConfigs).subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - expect(spy).not.toHaveBeenCalled(); - }, - }); + try { + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(spy).not.toHaveBeenCalled(); + } }); it('uses first/default config when no param is passed', async () => { @@ -153,12 +155,14 @@ describe('CheckAuthService', () => { ]; const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalledExactlyOnceWith( - { configId: 'configId1', authority: 'some-authority' }, - allConfigs, - undefined - ); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith( + { configId: 'configId1', authority: 'some-authority' }, + allConfigs, + undefined + ); }); it('returns null and sendMessageToMainWindow if currently in a popup', async () => { @@ -180,17 +184,18 @@ expect(spy).toHaveBeenCalledExactlyOnceWith( vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true); const popupSpy = vi.spyOn(popUpService, 'sendMessageToMainWindow'); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: false, - errorMessage: '', - userData: null, - idToken: '', - accessToken: '', - configId: '', - });; -expect(popupSpy).toHaveBeenCalled(); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: false, + errorMessage: '', + userData: null, + idToken: '', + accessToken: '', + configId: '', + }); + expect(popupSpy).toHaveBeenCalled(); }); it('returns isAuthenticated: false with error message in case handleCallbackAndFireEvents throws an error', async () => { @@ -211,17 +216,18 @@ expect(popupSpy).toHaveBeenCalled(); 'http://localhost:4200' ); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: false, - errorMessage: 'ERROR', - configId: 'configId1', - idToken: '', - userData: null, - accessToken: '', - });; -expect(spy).toHaveBeenCalled(); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: false, + errorMessage: 'ERROR', + configId: 'configId1', + idToken: '', + userData: null, + accessToken: '', + }); + expect(spy).toHaveBeenCalled(); }); it('calls callbackService.handlePossibleStsCallback with current url when callback is true', async () => { @@ -243,16 +249,17 @@ expect(spy).toHaveBeenCalled(); .spyOn(callBackService, 'handleCallbackAndFireEvents') .mockReturnValue(of({} as CallbackContext)); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: true, - userData: undefined, - accessToken: 'at', - configId: 'configId1', - idToken: 'idt', - });; -expect(spy).toHaveBeenCalled(); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: true, + userData: undefined, + accessToken: 'at', + configId: 'configId1', + idToken: 'idt', + }); + expect(spy).toHaveBeenCalled(); }); it('does NOT call handleCallbackAndFireEvents with current url when callback is false', async () => { @@ -275,16 +282,17 @@ expect(spy).toHaveBeenCalled(); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue('at'); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('idt'); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: true, - userData: undefined, - accessToken: 'at', - configId: 'configId1', - idToken: 'idt', - });; -expect(spy).not.toHaveBeenCalled(); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: true, + userData: undefined, + accessToken: 'at', + configId: 'configId1', + idToken: 'idt', + }); + expect(spy).not.toHaveBeenCalled(); }); it('does fire the auth and user data events when it is not a callback from the security token service and is authenticated', async () => { @@ -314,19 +322,20 @@ expect(spy).not.toHaveBeenCalled(); ); const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists'); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: true, - userData: { - some: 'user-data', - }, - accessToken: 'at', - configId: 'configId1', - idToken: 'idt', - });; -expect(setAuthorizedAndFireEventSpy).toHaveBeenCalled();; -expect(userServiceSpy).toHaveBeenCalled(); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: true, + userData: { + some: 'user-data', + }, + accessToken: 'at', + configId: 'configId1', + idToken: 'idt', + }); + expect(setAuthorizedAndFireEventSpy).toHaveBeenCalled(); + expect(userServiceSpy).toHaveBeenCalled(); }); it('does NOT fire the auth and user data events when it is not a callback from the security token service and is NOT authenticated', async () => { @@ -353,17 +362,18 @@ expect(userServiceSpy).toHaveBeenCalled(); ); const userServiceSpy = vi.spyOn(userService, 'publishUserDataIfExists'); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: false, - userData: undefined, - accessToken: 'at', - configId: 'configId1', - idToken: 'it', - });; -expect(setAuthorizedAndFireEventSpy).not.toHaveBeenCalled();; -expect(userServiceSpy).not.toHaveBeenCalled(); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: false, + userData: undefined, + accessToken: 'at', + configId: 'configId1', + idToken: 'it', + }); + expect(setAuthorizedAndFireEventSpy).not.toHaveBeenCalled(); + expect(userServiceSpy).not.toHaveBeenCalled(); }); it('if authenticated return true', async () => { @@ -383,15 +393,16 @@ expect(userServiceSpy).not.toHaveBeenCalled(); true ); - const result = await lastValueFrom(checkAuthService - .checkAuth(allConfigs[0]!, allConfigs)); -expect(result).toEqual({ - isAuthenticated: true, - userData: undefined, - accessToken: 'at', - configId: 'configId1', - idToken: 'idt', - }); + const result = await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(result).toEqual({ + isAuthenticated: true, + userData: undefined, + accessToken: 'at', + configId: 'configId1', + idToken: 'idt', + }); }); it('if authenticated set auth and fires event ', async () => { @@ -409,8 +420,10 @@ expect(result).toEqual({ const spy = vi.spyOn(authStateService, 'setAuthenticatedAndFireEvent'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalled(); }); it('if authenticated publishUserdataIfExists', async () => { @@ -430,8 +443,10 @@ expect(spy).toHaveBeenCalled(); const spy = vi.spyOn(userService, 'publishUserDataIfExists'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalled(); }); it('if authenticated callbackService startTokenValidationPeriodically', async () => { @@ -455,8 +470,10 @@ expect(spy).toHaveBeenCalled(); 'startTokenValidationPeriodically' ); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalled(); }); it('if isCheckSessionConfigured call checkSessionService.start()', async () => { @@ -478,8 +495,10 @@ expect(spy).toHaveBeenCalled(); ); const spy = vi.spyOn(checkSessionService, 'start'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalled(); }); it('if isSilentRenewConfigured call getOrCreateIframe()', async () => { @@ -501,8 +520,10 @@ expect(spy).toHaveBeenCalled(); ); const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalled(); }); it('calls checkSavedRedirectRouteAndNavigate if authenticated', async () => { @@ -524,9 +545,11 @@ expect(spy).toHaveBeenCalled(); 'checkSavedRedirectRouteAndNavigate' ); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalledTimes(1);; -expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); }); it('does not call checkSavedRedirectRouteAndNavigate if not authenticated', async () => { @@ -545,8 +568,10 @@ expect(spy).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); 'checkSavedRedirectRouteAndNavigate' ); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalledTimes(0); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalledTimes(0); }); it('fires CheckingAuth-Event on start and finished event on end', async () => { @@ -563,11 +588,13 @@ expect(spy).toHaveBeenCalledTimes(0); const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(fireEventSpy).toHaveBeenCalledWith([ - [EventTypes.CheckingAuth], - [EventTypes.CheckingAuthFinished], - ]); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(fireEventSpy).toHaveBeenCalledWith([ + [EventTypes.CheckingAuth], + [EventTypes.CheckingAuthFinished], + ]); }); it('fires CheckingAuth-Event on start and CheckingAuthFinishedWithError event on end if exception occurs', async () => { @@ -584,11 +611,13 @@ expect(fireEventSpy).toHaveBeenCalledWith([ 'http://localhost:4200' ); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(fireEventSpy).toHaveBeenCalledWith([ - [EventTypes.CheckingAuth], - [EventTypes.CheckingAuthFinishedWithError, 'ERROR'], - ]); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(fireEventSpy).toHaveBeenCalledWith([ + [EventTypes.CheckingAuth], + [EventTypes.CheckingAuthFinishedWithError, 'ERROR'], + ]); }); it('fires CheckingAuth-Event on start and finished event on end if not authenticated', async () => { @@ -605,11 +634,13 @@ expect(fireEventSpy).toHaveBeenCalledWith([ const fireEventSpy = vi.spyOn(publicEventsService, 'fireEvent'); - await lastValueFrom(checkAuthService.checkAuth(allConfigs[0]!, allConfigs)); -expect(fireEventSpy).toBeCalledWith([ - [EventTypes.CheckingAuth], - [EventTypes.CheckingAuthFinished], - ]); + await lastValueFrom( + checkAuthService.checkAuth(allConfigs[0]!, allConfigs) + ); + expect(fireEventSpy).toBeCalledWith([ + [EventTypes.CheckingAuth], + [EventTypes.CheckingAuthFinished], + ]); }); }); @@ -634,9 +665,10 @@ expect(fireEventSpy).toBeCalledWith([ ); const spy = vi.spyOn(silentRenewService, 'getOrCreateIframe'); - await lastValueFrom(checkAuthService - .checkAuthIncludingServer(allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalled(); }); it('does forceRefreshSession get called and is NOT authenticated', async () => { @@ -662,9 +694,10 @@ expect(spy).toHaveBeenCalled(); }) ); - const result = await lastValueFrom(checkAuthService - .checkAuthIncludingServer(allConfigs[0]!, allConfigs)); -expect(result).toBeTruthy(); + const result = await lastValueFrom( + checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs) + ); + expect(result).toBeTruthy(); }); it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing with silentrenew', async () => { @@ -709,15 +742,16 @@ expect(result).toBeTruthy(); }) ); - await lastValueFrom(checkAuthService - .checkAuthIncludingServer(allConfigs[0]!, allConfigs)); -expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( - allConfigs[0] - );; -expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1);; -expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith( - allConfigs[0] - ); + await lastValueFrom( + checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs) + ); + expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0] + ); + expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1); + expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0] + ); }); it('should start check session and validation after forceRefreshSession has been called and is authenticated after forcing without silentrenew', async () => { @@ -762,13 +796,14 @@ expect(getOrCreateIframeSpy).toHaveBeenCalledExactlyOnceWith( }) ); - await lastValueFrom(checkAuthService - .checkAuthIncludingServer(allConfigs[0]!, allConfigs)); -expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( - allConfigs[0] - );; -expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1);; -expect(getOrCreateIframeSpy).not.toHaveBeenCalled(); + await lastValueFrom( + checkAuthService.checkAuthIncludingServer(allConfigs[0]!, allConfigs) + ); + expect(checkSessionServiceStartSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0] + ); + expect(periodicallyTokenCheckServiceSpy).toHaveBeenCalledTimes(1); + expect(getOrCreateIframeSpy).not.toHaveBeenCalled(); }); }); @@ -790,19 +825,21 @@ expect(getOrCreateIframeSpy).not.toHaveBeenCalled(); ); const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - const result = await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); -expect(Array.isArray(result)).toBe(true);; -expect(spy).toHaveBeenCalledTimes(2);; -expect(vi.mocked(spy).mock.calls[0]).toEqual([ - allConfigs[0]!, - allConfigs, - undefined, - ]);; -expect(vi.mocked(spy).mock.calls[1]).toEqual([ - allConfigs[1], - allConfigs, - undefined, - ]); + const result = await lastValueFrom( + checkAuthService.checkAuthMultiple(allConfigs) + ); + expect(Array.isArray(result)).toBe(true); + expect(spy).toHaveBeenCalledTimes(2); + expect(vi.mocked(spy).mock.calls[0]).toEqual([ + allConfigs[0]!, + allConfigs, + undefined, + ]); + expect(vi.mocked(spy).mock.calls[1]).toEqual([ + allConfigs[1], + allConfigs, + undefined, + ]); }); it('uses config from passed configId if configId was passed and returns all results', async () => { @@ -818,20 +855,22 @@ expect(vi.mocked(spy).mock.calls[1]).toEqual([ const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - const result = await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); -expect(Array.isArray(result)).toBe(true);; -expect(spy).toBeCalledWith([ - [ - { configId: 'configId1', authority: 'some-authority1' }, - allConfigs, - undefined, - ], - [ - { configId: 'configId2', authority: 'some-authority2' }, - allConfigs, - undefined, - ], - ]); + const result = await lastValueFrom( + checkAuthService.checkAuthMultiple(allConfigs) + ); + expect(Array.isArray(result)).toBe(true); + expect(spy).toBeCalledWith([ + [ + { configId: 'configId1', authority: 'some-authority1' }, + allConfigs, + undefined, + ], + [ + { configId: 'configId2', authority: 'some-authority2' }, + allConfigs, + undefined, + ], + ]); }); it('runs through all configs if no parameter is passed and has no state in url', async () => { @@ -847,19 +886,21 @@ expect(spy).toBeCalledWith([ const spy = vi.spyOn(checkAuthService as any, 'checkAuthWithConfig'); - const result = await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); -expect(Array.isArray(result)).toBe(true);; -expect(spy).toHaveBeenCalledTimes(2);; -expect(vi.mocked(spy).mock.calls[0]).toEqual([ - { configId: 'configId1', authority: 'some-authority1' }, - allConfigs, - undefined, - ]);; -expect(vi.mocked(spy).mock.calls[1]).toEqual([ - { configId: 'configId2', authority: 'some-authority2' }, - allConfigs, - undefined, - ]); + const result = await lastValueFrom( + checkAuthService.checkAuthMultiple(allConfigs) + ); + expect(Array.isArray(result)).toBe(true); + expect(spy).toHaveBeenCalledTimes(2); + expect(vi.mocked(spy).mock.calls[0]).toEqual([ + { configId: 'configId1', authority: 'some-authority1' }, + allConfigs, + undefined, + ]); + expect(vi.mocked(spy).mock.calls[1]).toEqual([ + { configId: 'configId2', authority: 'some-authority2' }, + allConfigs, + undefined, + ]); }); it('throws error if url has state param but no config could be found', async () => { @@ -870,13 +911,13 @@ expect(vi.mocked(spy).mock.calls[1]).toEqual([ const allConfigs: OpenIdConfiguration[] = []; - checkAuthService.checkAuthMultiple(allConfigs).subscribe({ - error: (error) => { - expect(error.message).toEqual( - 'could not find matching config for state the-state-param' - ); - }, - }); + try { + await lastValueFrom(checkAuthService.checkAuthMultiple(allConfigs)); + } catch (error: any) { + expect(error.message).toEqual( + 'could not find matching config for state the-state-param' + ); + } }); }); }); diff --git a/src/auth-state/check-auth.service.ts b/src/auth-state/check-auth.service.ts index b2d6d9c..a8c72c9 100644 --- a/src/auth-state/check-auth.service.ts +++ b/src/auth-state/check-auth.service.ts @@ -57,7 +57,7 @@ export class CheckAuthService { const stateParamFromUrl = this.currentUrlService.getStateParamFromCurrentUrl(url); - return Boolean(stateParamFromUrl) + return stateParamFromUrl ? this.getConfigurationWithUrlState([configuration], stateParamFromUrl) : configuration; } diff --git a/src/auth.module.spec.ts b/src/auth.module.spec.ts index 2507550..7c7e7c7 100644 --- a/src/auth.module.spec.ts +++ b/src/auth.module.spec.ts @@ -1,6 +1,5 @@ import { TestBed } from '@/testing'; import { of } from 'rxjs'; -import { vi } from 'vitest'; import { PASSED_CONFIG } from './auth-config'; import { AuthModule } from './auth.module'; import { ConfigurationService } from './config/config.service'; diff --git a/src/auto-login/auto-login-partial-routes.guard.spec.ts b/src/auto-login/auto-login-partial-routes.guard.spec.ts index a4a791c..35cd4c0 100644 --- a/src/auto-login/auto-login-partial-routes.guard.spec.ts +++ b/src/auto-login/auto-login-partial-routes.guard.spec.ts @@ -4,7 +4,7 @@ import { type ActivatedRouteSnapshot, type RouterStateSnapshot, } from 'oidc-client-rx'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { CheckAuthService } from '../auth-state/check-auth.service'; diff --git a/src/auto-login/auto-login.service.spec.ts b/src/auto-login/auto-login.service.spec.ts index f738c47..061735b 100644 --- a/src/auto-login/auto-login.service.spec.ts +++ b/src/auto-login/auto-login.service.spec.ts @@ -16,9 +16,6 @@ describe('AutoLoginService ', () => { imports: [RouterTestingModule], providers: [AutoLoginService, mockProvider(StoragePersistenceService)], }); - }); - - beforeEach(() => { router = TestBed.inject(Router); autoLoginService = TestBed.inject(AutoLoginService); storagePersistenceService = TestBed.inject(StoragePersistenceService); diff --git a/src/auto-login/auto-login.service.ts b/src/auto-login/auto-login.service.ts index e58f322..c6a4c35 100644 --- a/src/auto-login/auto-login.service.ts +++ b/src/auto-login/auto-login.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from 'injection-js'; import { Router } from '@angular/router'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; const STORAGE_KEY = 'redirect'; diff --git a/src/callback/code-flow-callback.service.spec.ts b/src/callback/code-flow-callback.service.spec.ts index ff979d3..cd89000 100644 --- a/src/callback/code-flow-callback.service.spec.ts +++ b/src/callback/code-flow-callback.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed, mockRouterProvider } from '@/testing'; import { AbstractRouter } from 'oidc-client-rx'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; @@ -85,13 +85,18 @@ describe('CodeFlowCallbackService ', () => { triggerAuthorizationResultEvent: true, }; - await lastValueFrom(codeFlowCallbackService - .authenticatedCallbackWithCode('some-url2', config, [config])); -expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [ - config, - ]);; -expect(routerSpy).not.toHaveBeenCalled();; -expect(flowsDataSpy).toHaveBeenCalled(); + await lastValueFrom( + codeFlowCallbackService.authenticatedCallbackWithCode( + 'some-url2', + config, + [config] + ) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-url2', config, [ + config, + ]); + expect(routerSpy).not.toHaveBeenCalled(); + expect(flowsDataSpy).toHaveBeenCalled(); }); it('calls router and resetCodeFlowInProgress if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => { @@ -120,13 +125,18 @@ expect(flowsDataSpy).toHaveBeenCalled(); postLoginRoute: 'postLoginRoute', }; - await lastValueFrom(codeFlowCallbackService - .authenticatedCallbackWithCode('some-url3', config, [config])); -expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [ - config, - ]);; -expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute');; -expect(flowsDataSpy).toHaveBeenCalled(); + await lastValueFrom( + codeFlowCallbackService.authenticatedCallbackWithCode( + 'some-url3', + config, + [config] + ) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith('some-url3', config, [ + config, + ]); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); + expect(flowsDataSpy).toHaveBeenCalled(); }); it('resetSilentRenewRunning, resetCodeFlowInProgress and stopPeriodicallTokenCheck in case of error', async () => { @@ -152,16 +162,20 @@ expect(flowsDataSpy).toHaveBeenCalled(); postLoginRoute: 'postLoginRoute', }; - codeFlowCallbackService - .authenticatedCallbackWithCode('some-url4', config, [config]) - .subscribe({ - error: (err) => { - expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - expect(resetCodeFlowInProgressSpy).toHaveBeenCalled(); - expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + codeFlowCallbackService.authenticatedCallbackWithCode( + 'some-url4', + config, + [config] + ) + ); + } catch (err: any) { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(resetCodeFlowInProgressSpy).toHaveBeenCalled(); + expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); it(`navigates to unauthorizedRoute in case of error and in case of error and @@ -186,18 +200,20 @@ expect(flowsDataSpy).toHaveBeenCalled(); unauthorizedRoute: 'unauthorizedRoute', }; - codeFlowCallbackService - .authenticatedCallbackWithCode('some-url5', config, [config]) - .subscribe({ - error: (err) => { - expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - expect(routerSpy).toHaveBeenCalledExactlyOnceWith( - 'unauthorizedRoute' - ); - }, - }); + try { + await lastValueFrom( + codeFlowCallbackService.authenticatedCallbackWithCode( + 'some-url5', + config, + [config] + ) + ); + } catch (err: any) { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute'); + } }); }); }); diff --git a/src/callback/implicit-flow-callback.service.spec.ts b/src/callback/implicit-flow-callback.service.spec.ts index 94f697d..a2bfe5b 100644 --- a/src/callback/implicit-flow-callback.service.spec.ts +++ b/src/callback/implicit-flow-callback.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed, mockRouterProvider } from '@/testing'; import { AbstractRouter } from 'oidc-client-rx'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import type { CallbackContext } from '../flows/callback-context'; import { FlowsDataService } from '../flows/flows-data.service'; @@ -81,14 +81,19 @@ describe('ImplicitFlowCallbackService ', () => { triggerAuthorizationResultEvent: true, }; - await lastValueFrom(implicitFlowCallbackService - .authenticatedImplicitFlowCallback(config, [config], 'some-hash')); -expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - 'some-hash' - );; -expect(routerSpy).not.toHaveBeenCalled(); + await lastValueFrom( + implicitFlowCallbackService.authenticatedImplicitFlowCallback( + config, + [config], + 'some-hash' + ) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + 'some-hash' + ); + expect(routerSpy).not.toHaveBeenCalled(); }); it('calls router if triggerAuthorizationResultEvent is false and isRenewProcess is false', async () => { @@ -113,14 +118,19 @@ expect(routerSpy).not.toHaveBeenCalled(); postLoginRoute: 'postLoginRoute', }; - await lastValueFrom(implicitFlowCallbackService - .authenticatedImplicitFlowCallback(config, [config], 'some-hash')); -expect(spy).toHaveBeenCalledExactlyOnceWith( - config, - [config], - 'some-hash' - );; -expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); + await lastValueFrom( + implicitFlowCallbackService.authenticatedImplicitFlowCallback( + config, + [config], + 'some-hash' + ) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith( + config, + [config], + 'some-hash' + ); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); }); it('resetSilentRenewRunning and stopPeriodicallyTokenCheck in case of error', async () => { @@ -141,15 +151,19 @@ expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); postLoginRoute: 'postLoginRoute', }; - implicitFlowCallbackService - .authenticatedImplicitFlowCallback(config, [config], 'some-hash') - .subscribe({ - error: (err) => { - expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + implicitFlowCallbackService.authenticatedImplicitFlowCallback( + config, + [config], + 'some-hash' + ) + ); + } catch (err: any) { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); it(`navigates to unauthorizedRoute in case of error and in case of error and @@ -173,18 +187,20 @@ expect(routerSpy).toHaveBeenCalledExactlyOnceWith('postLoginRoute'); unauthorizedRoute: 'unauthorizedRoute', }; - implicitFlowCallbackService - .authenticatedImplicitFlowCallback(config, [config], 'some-hash') - .subscribe({ - error: (err) => { - expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - expect(routerSpy).toHaveBeenCalledExactlyOnceWith( - 'unauthorizedRoute' - ); - }, - }); + try { + await lastValueFrom( + implicitFlowCallbackService.authenticatedImplicitFlowCallback( + config, + [config], + 'some-hash' + ) + ); + } catch (err: any) { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(stopPeriodicallTokenCheckSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + expect(routerSpy).toHaveBeenCalledExactlyOnceWith('unauthorizedRoute'); + } }); }); }); diff --git a/src/callback/interval.service.spec.ts b/src/callback/interval.service.spec.ts index 25568c0..5710e50 100644 --- a/src/callback/interval.service.spec.ts +++ b/src/callback/interval.service.spec.ts @@ -1,4 +1,4 @@ -import { TestBed, fakeAsync, tick } from '@/testing'; +import { TestBed } from '@/testing'; import { Subscription } from 'rxjs'; import { vi } from 'vitest'; import { IntervalService } from './interval.service'; @@ -19,9 +19,6 @@ describe('IntervalService', () => { }, ], }); - }); - - beforeEach(() => { intervalService = TestBed.inject(IntervalService); }); @@ -60,15 +57,15 @@ describe('IntervalService', () => { describe('startPeriodicTokenCheck', () => { it('starts check after correct milliseconds', async () => { const periodicCheck = intervalService.startPeriodicTokenCheck(0.5); - const spy = jasmine.createSpy(); + const spy = vi.fn(); const sub = periodicCheck.subscribe(() => { spy(); }); - tick(500); + await vi.advanceTimersByTimeAsync(500); expect(spy).toHaveBeenCalledTimes(1); - tick(500); + await vi.advanceTimersByTimeAsync(500); expect(spy).toHaveBeenCalledTimes(2); sub.unsubscribe(); diff --git a/src/callback/refresh-session-refresh-token.service.spec.ts b/src/callback/refresh-session-refresh-token.service.spec.ts index d44f54c..b411b3a 100644 --- a/src/callback/refresh-session-refresh-token.service.spec.ts +++ b/src/callback/refresh-session-refresh-token.service.spec.ts @@ -62,16 +62,17 @@ describe('RefreshSessionRefreshTokenService', () => { 'resetAuthorizationData' ); - refreshSessionRefreshTokenService - .refreshSessionWithRefreshTokens({ configId: 'configId1' }, [ - { configId: 'configId1' }, - ]) - .subscribe({ - error: (err) => { - expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens( + { configId: 'configId1' }, + [{ configId: 'configId1' }] + ) + ); + } catch (err: any) { + expect(resetSilentRenewRunningSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); it('finalize with stopPeriodicTokenCheck in case of error', async () => { @@ -83,15 +84,16 @@ describe('RefreshSessionRefreshTokenService', () => { 'stopPeriodicTokenCheck' ); - refreshSessionRefreshTokenService - .refreshSessionWithRefreshTokens({ configId: 'configId1' }, [ - { configId: 'configId1' }, - ]) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens( + { configId: 'configId1' }, + [{ configId: 'configId1' }] + ) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } await vi.advanceTimersByTimeAsync(0); expect(stopPeriodicallyTokenCheckSpy).toHaveBeenCalled(); }); diff --git a/src/callback/refresh-session.service.spec.ts b/src/callback/refresh-session.service.spec.ts index 9e4a31e..ceb35d1 100644 --- a/src/callback/refresh-session.service.spec.ts +++ b/src/callback/refresh-session.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed, spyOnProperty } from '@/testing'; -import { lastValueFrom, of, throwError } from 'rxjs'; +import { EmptyError, lastValueFrom, of, throwError } from 'rxjs'; import { delay } from 'rxjs/operators'; import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; @@ -216,18 +216,22 @@ describe('RefreshSessionService ', () => { }, ]; - refreshSessionService - .userForceRefreshSession(allConfigs[0]!, allConfigs) - .subscribe({ - error: () => { - expect.fail('It should not return any error.'); - }, - complete: () => { - expect( - flowsDataService.resetSilentRenewRunning - ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); - }, - }); + try { + await lastValueFrom( + refreshSessionService.userForceRefreshSession( + allConfigs[0]!, + allConfigs + ) + ); + } catch (err: any) { + if (err instanceof EmptyError) { + expect( + flowsDataService.resetSilentRenewRunning + ).toHaveBeenCalledExactlyOnceWith(allConfigs[0]); + } else { + expect.fail('It should not return any error.'); + } + } }); }); @@ -448,18 +452,16 @@ describe('RefreshSessionService ', () => { 'resetSilentRenewRunning' ); - refreshSessionService - .forceRefreshSession(allConfigs[0]!, allConfigs) - .subscribe({ - next: () => { - expect.fail('It should not return any result.'); - }, - error: (error) => { - expect(error).toBeInstanceOf(Error); - expect(error.message).toEqual(`Error: ${expectedErrorMessage}`); - expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled(); - }, - }); + try { + await lastValueFrom( + refreshSessionService.forceRefreshSession(allConfigs[0]!, allConfigs) + ); + expect.fail('It should not return any result.'); + } catch (error: any) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toEqual(`Error: ${expectedErrorMessage}`); + expect(resetSilentRenewRunningSpy).not.toHaveBeenCalled(); + } }); describe('NOT isCurrentFlowCodeFlowWithRefreshTokens', () => { diff --git a/src/config/auth-well-known/auth-well-known-data.service.spec.ts b/src/config/auth-well-known/auth-well-known-data.service.spec.ts index 0f781ac..38eed06 100644 --- a/src/config/auth-well-known/auth-well-known-data.service.spec.ts +++ b/src/config/auth-well-known/auth-well-known-data.service.spec.ts @@ -39,9 +39,6 @@ describe('AuthWellKnownDataService', () => { mockProvider(LoggerService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(AuthWellKnownDataService); loggerService = TestBed.inject(LoggerService); dataService = TestBed.inject(DataService); @@ -73,7 +70,7 @@ describe('AuthWellKnownDataService', () => { const dataServiceSpy = vi .spyOn(dataService, 'get') .mockReturnValue(of(null)); - const urlWithSuffix = `myUrl/.well-known/openid-configuration`; + const urlWithSuffix = 'myUrl/.well-known/openid-configuration'; await lastValueFrom( (service as any).getWellKnownDocument(urlWithSuffix, { @@ -89,7 +86,8 @@ describe('AuthWellKnownDataService', () => { const dataServiceSpy = vi .spyOn(dataService, 'get') .mockReturnValue(of(null)); - const urlWithSuffix = `myUrl/.well-known/openid-configuration/and/some/more/stuff`; + const urlWithSuffix = + 'myUrl/.well-known/openid-configuration/and/some/more/stuff'; await lastValueFrom( (service as any).getWellKnownDocument(urlWithSuffix, { @@ -105,7 +103,7 @@ describe('AuthWellKnownDataService', () => { const dataServiceSpy = vi .spyOn(dataService, 'get') .mockReturnValue(of(null)); - const urlWithoutSuffix = `myUrl`; + const urlWithoutSuffix = 'myUrl'; const urlWithSuffix = `${urlWithoutSuffix}/.well-known/test-openid-configuration`; await lastValueFrom( @@ -128,14 +126,13 @@ describe('AuthWellKnownDataService', () => { ) ); - (service as any) - .getWellKnownDocument('anyurl', { configId: 'configId1' }) - .subscribe({ - next: (res: unknown) => { - expect(res).toBeTruthy(); - expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); - }, - }); + const res: unknown = await lastValueFrom( + (service as any).getWellKnownDocument('anyurl', { + configId: 'configId1', + }) + ); + expect(res).toBeTruthy(); + expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); }); it('should retry twice', async () => { @@ -147,14 +144,13 @@ describe('AuthWellKnownDataService', () => { ) ); - (service as any) - .getWellKnownDocument('anyurl', { configId: 'configId1' }) - .subscribe({ - next: (res: any) => { - expect(res).toBeTruthy(); - expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); - }, - }); + const res: any = await lastValueFrom( + (service as any).getWellKnownDocument('anyurl', { + configId: 'configId1', + }) + ); + expect(res).toBeTruthy(); + expect(res).toEqual(DUMMY_WELL_KNOWN_DOCUMENT); }); it('should fail after three tries', async () => { @@ -167,11 +163,13 @@ describe('AuthWellKnownDataService', () => { ) ); - (service as any).getWellKnownDocument('anyurl', 'configId').subscribe({ - error: (err: unknown) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + (service as any).getWellKnownDocument('anyurl', 'configId') + ); + } catch (err: unknown) { + expect(err).toBeTruthy(); + } }); }); @@ -181,7 +179,7 @@ describe('AuthWellKnownDataService', () => { of({ jwks_uri: 'jwks_uri' }) ); - const spy = vi.spyOn(service as any, 'getWellKnownDocument')(); + const spy = vi.spyOn(service as any, 'getWellKnownDocument'); const result = await lastValueFrom( service.getWellKnownEndPointsForConfig({ @@ -201,15 +199,15 @@ describe('AuthWellKnownDataService', () => { authWellknownEndpointUrl: undefined, }; - service.getWellKnownEndPointsForConfig(config).subscribe({ - error: (error) => { - expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'no authWellknownEndpoint given!' - ); - expect(error.message).toEqual('no authWellknownEndpoint given!'); - }, - }); + try { + await lastValueFrom(service.getWellKnownEndPointsForConfig(config)); + } catch (error: any) { + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'no authWellknownEndpoint given!' + ); + expect(error.message).toEqual('no authWellknownEndpoint given!'); + } }); it('should merge the mapped endpoints with the provided endpoints', async () => { @@ -233,7 +231,7 @@ describe('AuthWellKnownDataService', () => { }, }) ); - expect(result).toEqual(jasmine.objectContaining(expected)); + expect(result).toEqual(expect.objectContaining(expected)); }); }); }); diff --git a/src/config/auth-well-known/auth-well-known-data.service.ts b/src/config/auth-well-known/auth-well-known-data.service.ts index 426e7c1..78eeec2 100644 --- a/src/config/auth-well-known/auth-well-known-data.service.ts +++ b/src/config/auth-well-known/auth-well-known-data.service.ts @@ -1,12 +1,12 @@ import { inject, Injectable } from 'injection-js'; -import { Observable, throwError } from 'rxjs'; +import { type Observable, throwError } from 'rxjs'; import { map, retry } from 'rxjs/operators'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; -import { OpenIdConfiguration } from '../openid-configuration'; -import { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; +import type { OpenIdConfiguration } from '../openid-configuration'; +import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; -const WELL_KNOWN_SUFFIX = `/.well-known/openid-configuration`; +const WELL_KNOWN_SUFFIX = '/.well-known/openid-configuration'; @Injectable() export class AuthWellKnownDataService { diff --git a/src/config/auth-well-known/auth-well-known.service.spec.ts b/src/config/auth-well-known/auth-well-known.service.spec.ts index f96fe88..146c2e9 100644 --- a/src/config/auth-well-known/auth-well-known.service.spec.ts +++ b/src/config/auth-well-known/auth-well-known.service.spec.ts @@ -35,15 +35,15 @@ describe('AuthWellKnownService', () => { describe('getAuthWellKnownEndPoints', () => { it('getAuthWellKnownEndPoints throws an error if not config provided', async () => { - service.queryAndStoreAuthWellKnownEndPoints(null).subscribe({ - error: (error) => { - expect(error).toEqual( - new Error( - 'Please provide a configuration before setting up the module' - ) - ); - }, - }); + try { + await lastValueFrom(service.queryAndStoreAuthWellKnownEndPoints(null)); + } catch (error) { + expect(error).toEqual( + new Error( + 'Please provide a configuration before setting up the module' + ) + ); + } }); it('getAuthWellKnownEndPoints calls always dataservice', async () => { @@ -91,18 +91,18 @@ describe('AuthWellKnownService', () => { ); const publicEventsServiceSpy = vi.spyOn(publicEventsService, 'fireEvent'); - service - .queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1); - expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith( - EventTypes.ConfigLoadingFailed, - null - ); - }, - }); + try { + await lastValueFrom( + service.queryAndStoreAuthWellKnownEndPoints({ configId: 'configId1' }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(publicEventsServiceSpy).toHaveBeenCalledTimes(1); + expect(publicEventsServiceSpy).toHaveBeenCalledExactlyOnceWith( + EventTypes.ConfigLoadingFailed, + null + ); + } }); }); }); diff --git a/src/config/auth-well-known/auth-well-known.service.ts b/src/config/auth-well-known/auth-well-known.service.ts index d041763..2efe05f 100644 --- a/src/config/auth-well-known/auth-well-known.service.ts +++ b/src/config/auth-well-known/auth-well-known.service.ts @@ -1,12 +1,12 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { EventTypes } from '../../public-events/event-types'; import { PublicEventsService } from '../../public-events/public-events.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; -import { OpenIdConfiguration } from '../openid-configuration'; +import type { OpenIdConfiguration } from '../openid-configuration'; import { AuthWellKnownDataService } from './auth-well-known-data.service'; -import { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; +import type { AuthWellKnownEndpoints } from './auth-well-known-endpoints'; @Injectable() export class AuthWellKnownService { diff --git a/src/config/config.service.spec.ts b/src/config/config.service.spec.ts index 7998233..5fd4124 100644 --- a/src/config/config.service.spec.ts +++ b/src/config/config.service.spec.ts @@ -276,7 +276,9 @@ describe('Configuration Service', () => { false ); - await lastValueFrom(configService.getOpenIDConfigurations()); + const { allConfigs, currentConfig } = await lastValueFrom( + configService.getOpenIDConfigurations() + ); expect(allConfigs).toEqual([]); expect(currentConfig).toBeNull(); }); diff --git a/src/config/default-config.ts b/src/config/default-config.ts index fd8900b..d4e43f8 100644 --- a/src/config/default-config.ts +++ b/src/config/default-config.ts @@ -1,5 +1,5 @@ import { LogLevel } from '../logging/log-level'; -import { OpenIdConfiguration } from './openid-configuration'; +import type { OpenIdConfiguration } from './openid-configuration'; export const DEFAULT_CONFIG: OpenIdConfiguration = { authority: 'https://please_set', diff --git a/src/config/loader/config-loader.spec.ts b/src/config/loader/config-loader.spec.ts index 0d96379..688006a 100644 --- a/src/config/loader/config-loader.spec.ts +++ b/src/config/loader/config-loader.spec.ts @@ -1,4 +1,3 @@ -import { waitForAsync } from '@/testing'; import { lastValueFrom, of } from 'rxjs'; import type { OpenIdConfiguration } from '../openid-configuration'; import { StsConfigHttpLoader, StsConfigStaticLoader } from './config-loader'; @@ -46,8 +45,8 @@ describe('ConfigLoader', () => { const result = await lastValueFrom(result$); expect(Array.isArray(result)).toBeTruthy(); - expect(result[0].configId).toBe('configId1'); - expect(result[1].configId).toBe('configId2'); + expect(result[0]!.configId).toBe('configId1'); + expect(result[1]!.configId).toBe('configId2'); }); it('returns an array if an observable with a config array is passed', async () => { @@ -61,8 +60,8 @@ describe('ConfigLoader', () => { const result = await lastValueFrom(result$); expect(Array.isArray(result)).toBeTruthy(); - expect(result[0].configId).toBe('configId1'); - expect(result[1].configId).toBe('configId2'); + expect(result[0]!.configId).toBe('configId1'); + expect(result[1]!.configId).toBe('configId2'); }); it('returns an array if only one config is passed', async () => { @@ -74,7 +73,7 @@ describe('ConfigLoader', () => { const result = await lastValueFrom(result$); expect(Array.isArray(result)).toBeTruthy(); - expect(result[0].configId).toBe('configId1'); + expect(result[0]!.configId).toBe('configId1'); }); }); }); diff --git a/src/config/validation/config-validation.service.ts b/src/config/validation/config-validation.service.ts index bd79cd6..c82aeee 100644 --- a/src/config/validation/config-validation.service.ts +++ b/src/config/validation/config-validation.service.ts @@ -1,7 +1,7 @@ -import { inject, Injectable } from 'injection-js'; +import { Injectable, inject } from 'injection-js'; import { LoggerService } from '../../logging/logger.service'; -import { OpenIdConfiguration } from '../openid-configuration'; -import { Level, RuleValidationResult } from './rule'; +import type { OpenIdConfiguration } from '../openid-configuration'; +import type { Level, RuleValidationResult } from './rule'; import { allMultipleConfigRules, allRules } from './rules'; @Injectable() @@ -35,14 +35,14 @@ export class ConfigValidationService { let overallErrorCount = 0; - passedConfigs.forEach((passedConfig) => { + for (const passedConfig of passedConfigs) { const errorCount = this.processValidationResultsAndGetErrorCount( allValidationResults, passedConfig ); overallErrorCount += errorCount; - }); + } return overallErrorCount === 0; } @@ -75,12 +75,12 @@ export class ConfigValidationService { const allErrorMessages = this.getAllMessagesOfType('error', allMessages); const allWarnings = this.getAllMessagesOfType('warning', allMessages); - allErrorMessages.forEach((message) => - this.loggerService.logError(config, message) - ); - allWarnings.forEach((message) => - this.loggerService.logWarning(config, message) - ); + for (const message of allErrorMessages) { + this.loggerService.logError(config, message); + } + for (const message of allWarnings) { + this.loggerService.logWarning(config, message); + } return allErrorMessages.length; } diff --git a/src/config/validation/rule.ts b/src/config/validation/rule.ts index 69cbdd6..dbe382d 100644 --- a/src/config/validation/rule.ts +++ b/src/config/validation/rule.ts @@ -1,4 +1,4 @@ -import { OpenIdConfiguration } from '../openid-configuration'; +import type { OpenIdConfiguration } from '../openid-configuration'; export interface Rule { validate(passedConfig: OpenIdConfiguration): RuleValidationResult; diff --git a/src/config/validation/rules/ensure-authority.rule.ts b/src/config/validation/rules/ensure-authority.rule.ts index fd7fc09..ea568b9 100644 --- a/src/config/validation/rules/ensure-authority.rule.ts +++ b/src/config/validation/rules/ensure-authority.rule.ts @@ -1,5 +1,5 @@ -import { OpenIdConfiguration } from '../../openid-configuration'; -import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; +import type { OpenIdConfiguration } from '../../openid-configuration'; +import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule'; export const ensureAuthority = ( passedConfig: OpenIdConfiguration diff --git a/src/config/validation/rules/ensure-clientId.rule.ts b/src/config/validation/rules/ensure-clientId.rule.ts index 1396bcf..f0bfdac 100644 --- a/src/config/validation/rules/ensure-clientId.rule.ts +++ b/src/config/validation/rules/ensure-clientId.rule.ts @@ -1,5 +1,5 @@ -import { OpenIdConfiguration } from '../../openid-configuration'; -import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; +import type { OpenIdConfiguration } from '../../openid-configuration'; +import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule'; export const ensureClientId = ( passedConfig: OpenIdConfiguration diff --git a/src/config/validation/rules/ensure-no-duplicated-configs.rule.ts b/src/config/validation/rules/ensure-no-duplicated-configs.rule.ts index 4f9b4e7..7e2b00b 100644 --- a/src/config/validation/rules/ensure-no-duplicated-configs.rule.ts +++ b/src/config/validation/rules/ensure-no-duplicated-configs.rule.ts @@ -1,5 +1,5 @@ -import { OpenIdConfiguration } from '../../openid-configuration'; -import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; +import type { OpenIdConfiguration } from '../../openid-configuration'; +import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule'; const createIdentifierToCheck = (passedConfig: OpenIdConfiguration): string => { if (!passedConfig) { diff --git a/src/config/validation/rules/ensure-redirect-url.rule.ts b/src/config/validation/rules/ensure-redirect-url.rule.ts index 6cf7661..3d70155 100644 --- a/src/config/validation/rules/ensure-redirect-url.rule.ts +++ b/src/config/validation/rules/ensure-redirect-url.rule.ts @@ -1,5 +1,5 @@ -import { OpenIdConfiguration } from '../../openid-configuration'; -import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; +import type { OpenIdConfiguration } from '../../openid-configuration'; +import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule'; export const ensureRedirectRule = ( passedConfig: OpenIdConfiguration diff --git a/src/config/validation/rules/ensure-silentRenewUrl-with-no-refreshtokens.rule.ts b/src/config/validation/rules/ensure-silentRenewUrl-with-no-refreshtokens.rule.ts index 80fe4fd..eaa3937 100644 --- a/src/config/validation/rules/ensure-silentRenewUrl-with-no-refreshtokens.rule.ts +++ b/src/config/validation/rules/ensure-silentRenewUrl-with-no-refreshtokens.rule.ts @@ -1,5 +1,5 @@ -import { OpenIdConfiguration } from '../../openid-configuration'; -import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; +import type { OpenIdConfiguration } from '../../openid-configuration'; +import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule'; export const ensureSilentRenewUrlWhenNoRefreshTokenUsed = ( passedConfig: OpenIdConfiguration diff --git a/src/config/validation/rules/use-offline-scope-with-silent-renew.rule.ts b/src/config/validation/rules/use-offline-scope-with-silent-renew.rule.ts index 7160573..5659080 100644 --- a/src/config/validation/rules/use-offline-scope-with-silent-renew.rule.ts +++ b/src/config/validation/rules/use-offline-scope-with-silent-renew.rule.ts @@ -1,5 +1,5 @@ -import { OpenIdConfiguration } from '../../openid-configuration'; -import { POSITIVE_VALIDATION_RESULT, RuleValidationResult } from '../rule'; +import type { OpenIdConfiguration } from '../../openid-configuration'; +import { POSITIVE_VALIDATION_RESULT, type RuleValidationResult } from '../rule'; export const useOfflineScopeWithSilentRenew = ( passedConfig: OpenIdConfiguration diff --git a/src/extractors/jwk.extractor.spec.ts b/src/extractors/jwk.extractor.spec.ts index e4e9db6..44a62c3 100644 --- a/src/extractors/jwk.extractor.spec.ts +++ b/src/extractors/jwk.extractor.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; import { CryptoService } from '../utils/crypto/crypto.service'; import { JwkExtractor, @@ -94,9 +93,6 @@ describe('JwkExtractor', () => { imports: [], providers: [JwkExtractor, CryptoService], }); - }); - - beforeEach(() => { service = TestBed.inject(JwkExtractor); }); diff --git a/src/extractors/jwk.extractor.ts b/src/extractors/jwk.extractor.ts index ab099bf..d487d85 100644 --- a/src/extractors/jwk.extractor.ts +++ b/src/extractors/jwk.extractor.ts @@ -7,20 +7,20 @@ export class JwkExtractor { spec?: { kid?: string; use?: string; kty?: string }, throwOnEmpty = true ): JsonWebKey[] { - if (0 === keys.length) { + if (keys.length === 0) { throw JwkExtractorInvalidArgumentError; } const foundKeys = keys - .filter((k) => (spec?.kid ? (k as any)['kid'] === spec.kid : true)) - .filter((k) => (spec?.use ? k['use'] === spec.use : true)) - .filter((k) => (spec?.kty ? k['kty'] === spec.kty : true)); + .filter((k) => (spec?.kid ? (k as any).kid === spec.kid : true)) + .filter((k) => (spec?.use ? k.use === spec.use : true)) + .filter((k) => (spec?.kty ? k.kty === spec.kty : true)); if (foundKeys.length === 0 && throwOnEmpty) { throw JwkExtractorNoMatchingKeysError; } - if (foundKeys.length > 1 && (null === spec || undefined === spec)) { + if (foundKeys.length > 1 && (spec === null || undefined === spec)) { throw JwkExtractorSeveralMatchingKeysError; } @@ -29,7 +29,7 @@ export class JwkExtractor { } function buildErrorName(name: string): string { - return JwkExtractor.name + ': ' + name; + return `${JwkExtractor.name}: ${name}`; } export const JwkExtractorInvalidArgumentError = { diff --git a/src/flows/callback-context.ts b/src/flows/callback-context.ts index 87fbae0..6650996 100644 --- a/src/flows/callback-context.ts +++ b/src/flows/callback-context.ts @@ -1,5 +1,5 @@ -import { JwtKeys } from '../validation/jwtkeys'; -import { StateValidationResult } from '../validation/state-validation-result'; +import type { JwtKeys } from '../validation/jwtkeys'; +import type { StateValidationResult } from '../validation/state-validation-result'; export interface CallbackContext { code: string; diff --git a/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts b/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts index a14f7e2..b92e8e3 100644 --- a/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/code-flow-callback-handler.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { HttpErrorResponse, HttpHeaders } from '@ngify/http'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; @@ -32,9 +32,6 @@ describe('CodeFlowCallbackHandlerService', () => { mockProvider(DataService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(CodeFlowCallbackHandlerService); dataService = TestBed.inject(DataService); urlService = TestBed.inject(UrlService); @@ -58,13 +55,13 @@ describe('CodeFlowCallbackHandlerService', () => { () => '' ); - service - .codeFlowCallback('test-url', { configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.codeFlowCallback('test-url', { configId: 'configId1' }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('throws error if no code is given', async () => { @@ -72,15 +69,19 @@ describe('CodeFlowCallbackHandlerService', () => { .spyOn(urlService, 'getUrlParameter') .mockReturnValue('params'); - getUrlParameterSpy.withArgs('test-url', 'code').mockReturnValue(''); + mockImplementationWhenArgsEqual( + getUrlParameterSpy, + ['test-url', 'code'], + () => '' + ); - service - .codeFlowCallback('test-url', { configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.codeFlowCallback('test-url', { configId: 'configId1' }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('returns callbackContext if all params are good', async () => { @@ -98,9 +99,10 @@ describe('CodeFlowCallbackHandlerService', () => { existingIdToken: null, } as CallbackContext; - const callbackContext = await lastValueFrom(service - .codeFlowCallback('test-url', { configId: 'configId1' })); -expect(callbackContext).toEqual(expectedCallbackContext); + const callbackContext = await lastValueFrom( + service.codeFlowCallback('test-url', { configId: 'configId1' }) + ); + expect(callbackContext).toEqual(expectedCallbackContext); }); }); @@ -119,13 +121,15 @@ expect(callbackContext).toEqual(expectedCallbackContext); 'validateStateFromHashCallback' ).mockReturnValue(false); - service - .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, { + configId: 'configId1', + }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('throws error if authWellknownEndpoints is null is given', async () => { @@ -139,13 +143,15 @@ expect(callbackContext).toEqual(expectedCallbackContext); () => null ); - service - .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, { + configId: 'configId1', + }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('throws error if tokenendpoint is null is given', async () => { @@ -159,13 +165,15 @@ expect(callbackContext).toEqual(expectedCallbackContext); () => ({ tokenEndpoint: null }) ); - service - .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, { + configId: 'configId1', + }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('calls dataService if all params are good', async () => { @@ -182,14 +190,17 @@ expect(callbackContext).toEqual(expectedCallbackContext); 'validateStateFromHashCallback' ).mockReturnValue(true); - await lastValueFrom(service - .codeFlowCodeRequest({} as CallbackContext, { configId: 'configId1' })); -expect(postSpy).toHaveBeenCalledExactlyOnceWith( - 'tokenEndpoint', - undefined, - { configId: 'configId1' }, - expect.any(HttpHeaders) - ); + await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, { + configId: 'configId1', + }) + ); + expect(postSpy).toHaveBeenCalledExactlyOnceWith( + 'tokenEndpoint', + undefined, + { configId: 'configId1' }, + expect.any(HttpHeaders) + ); }); it('calls url service with custom token params', async () => { @@ -215,12 +226,13 @@ expect(postSpy).toHaveBeenCalledExactlyOnceWith( const postSpy = vi.spyOn(dataService, 'post').mockReturnValue(of({})); - await lastValueFrom(service - .codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config)); -expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, { - foo: 'bar', - });; -expect(postSpy).toHaveBeenCalledTimes(1); + await lastValueFrom( + service.codeFlowCodeRequest({ code: 'foo' } as CallbackContext, config) + ); + expect(urlServiceSpy).toHaveBeenCalledExactlyOnceWith('foo', config, { + foo: 'bar', + }); + expect(postSpy).toHaveBeenCalledTimes(1); }); it('calls dataService with correct headers if all params are good', async () => { @@ -241,13 +253,14 @@ expect(postSpy).toHaveBeenCalledTimes(1); 'validateStateFromHashCallback' ).mockReturnValue(true); - await lastValueFrom(service - .codeFlowCodeRequest({} as CallbackContext, config)); -const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; -expect(httpHeaders.has('Content-Type')).toBeTruthy();; -expect(httpHeaders.get('Content-Type')).toBe( - 'application/x-www-form-urlencoded' - ); + await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, config) + ); + const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTruthy(); + expect(httpHeaders.get('Content-Type')).toBe( + 'application/x-www-form-urlencoded' + ); }); it('returns error in case of http error', async () => { @@ -266,11 +279,13 @@ expect(httpHeaders.get('Content-Type')).toBe( () => ({ tokenEndpoint: 'tokenEndpoint' }) ); - service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, config) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('retries request in case of no connection http error and succeeds', async () => { @@ -297,16 +312,15 @@ expect(httpHeaders.get('Content-Type')).toBe( 'validateStateFromHashCallback' ).mockReturnValue(true); - service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(postSpy).toHaveBeenCalledTimes(1); - }, - error: (err) => { - // fails if there should be a result - expect(err).toBeFalsy(); - }, - }); + try { + const res = await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, config) + ); + expect(res).toBeTruthy(); + expect(postSpy).toHaveBeenCalledTimes(1); + } catch (err: any) { + expect(err).toBeFalsy(); + } }); it('retries request in case of no connection http error and fails because of http error afterwards', async () => { @@ -333,16 +347,15 @@ expect(httpHeaders.get('Content-Type')).toBe( 'validateStateFromHashCallback' ).mockReturnValue(true); - service.codeFlowCodeRequest({} as CallbackContext, config).subscribe({ - next: (res) => { - // fails if there should be a result - expect(res).toBeFalsy(); - }, - error: (err) => { - expect(err).toBeTruthy(); - expect(postSpy).toHaveBeenCalledTimes(1); - }, - }); + try { + const res = await lastValueFrom( + service.codeFlowCodeRequest({} as CallbackContext, config) + ); + expect(res).toBeFalsy(); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(postSpy).toHaveBeenCalledTimes(1); + } }); }); }); diff --git a/src/flows/callback-handling/code-flow-callback-handler.service.ts b/src/flows/callback-handling/code-flow-callback-handler.service.ts index a6af0c4..2df5334 100644 --- a/src/flows/callback-handling/code-flow-callback-handler.service.ts +++ b/src/flows/callback-handling/code-flow-callback-handler.service.ts @@ -1,14 +1,14 @@ import { HttpHeaders } from '@ngify/http'; import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError, timer } from 'rxjs'; +import { type Observable, of, throwError, timer } from 'rxjs'; import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators'; import { DataService } from '../../api/data.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { UrlService } from '../../utils/url/url.service'; import { TokenValidationService } from '../../validation/token-validation.service'; -import { AuthResult, CallbackContext } from '../callback-context'; +import type { AuthResult, CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; import { isNetworkError } from './error-helper'; diff --git a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts index e266536..b7385fa 100644 --- a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@/testing'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { LoggerService } from '../../logging/logger.service'; @@ -83,17 +83,18 @@ describe('HistoryJwtKeysCallbackHandlerService', () => { vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); - await lastValueFrom(service - .callbackHistoryAndResetJwtKeys( + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( callbackContext, allConfigs[0]!, allConfigs - )); -expect(storagePersistenceServiceSpy).toBeCalledWith([ - ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], - ['jwtKeys', { keys: [] }, allConfigs[0]], - ]);; -expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); + ) + ); + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['jwtKeys', { keys: [] }, allConfigs[0]], + ]); + expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); }); it('writes refresh_token into the storage without reuse (refresh token rotation)', async () => { @@ -120,17 +121,18 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); of({ keys: [] } as JwtKeys) ); - await lastValueFrom(service - .callbackHistoryAndResetJwtKeys( + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( callbackContext, allConfigs[0]!, allConfigs - )); -expect(storagePersistenceServiceSpy).toBeCalledWith([ - ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], - ['jwtKeys', { keys: [] }, allConfigs[0]], - ]);; -expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); + ) + ); + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['jwtKeys', { keys: [] }, allConfigs[0]], + ]); + expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); }); it('writes refresh_token into the storage with reuse (without refresh token rotation)', async () => { @@ -157,18 +159,19 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); - await lastValueFrom(service - .callbackHistoryAndResetJwtKeys( + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( callbackContext, allConfigs[0]!, allConfigs - )); -expect(storagePersistenceServiceSpy).toBeCalledWith([ - ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], - ['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]], - ['jwtKeys', { keys: [] }, allConfigs[0]], - ]);; -expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3); + ) + ); + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['reusable_refresh_token', 'dummy_refresh_token', allConfigs[0]], + ['jwtKeys', { keys: [] }, allConfigs[0]], + ]); + expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3); }); it('resetBrowserHistory if historyCleanup is turned on and is not in a renewProcess', async () => { @@ -191,13 +194,14 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(3); vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [] } as JwtKeys) ); - await lastValueFrom(service - .callbackHistoryAndResetJwtKeys( + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( callbackContext, allConfigs[0]!, allConfigs - )); -expect(windowSpy).toHaveBeenCalledTimes(1); + ) + ); + expect(windowSpy).toHaveBeenCalledTimes(1); }); it('returns callbackContext with jwtkeys filled if everything works fine', async () => { @@ -219,17 +223,18 @@ expect(windowSpy).toHaveBeenCalledTimes(1); vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({ keys: [{ kty: 'henlo' } as JwtKey] } as JwtKeys) ); - const result = await lastValueFrom(service - .callbackHistoryAndResetJwtKeys( + const result = await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( callbackContext, allConfigs[0]!, allConfigs - )); -expect(result).toEqual({ - isRenewProcess: false, - authResult: DUMMY_AUTH_RESULT, - jwtKeys: { keys: [{ kty: 'henlo' }] }, - } as CallbackContext); + ) + ); + expect(result).toEqual({ + isRenewProcess: false, + authResult: DUMMY_AUTH_RESULT, + jwtKeys: { keys: [{ kty: 'henlo' }] }, + } as CallbackContext); }); it('returns error if no jwtKeys have been in the call --> keys are null', async () => { @@ -251,19 +256,19 @@ expect(result).toEqual({ vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( of({} as JwtKeys) ); - service - .callbackHistoryAndResetJwtKeys( - callbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: (err) => { - expect(err.message).toEqual( - 'Failed to retrieve signing key with error: Error: Failed to retrieve signing key' - ); - }, - }); + try { + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + callbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch (err: any) { + expect(err.message).toEqual( + 'Failed to retrieve signing key with error: Error: Failed to retrieve signing key' + ); + } }); it('returns error if no jwtKeys have been in the call --> keys throw an error', async () => { @@ -284,19 +289,19 @@ expect(result).toEqual({ vi.spyOn(signInKeyDataService, 'getSigningKeys').mockReturnValue( throwError(() => new Error('error')) ); - service - .callbackHistoryAndResetJwtKeys( - callbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: (err) => { - expect(err.message).toEqual( - 'Failed to retrieve signing key with error: Error: Error: error' - ); - }, - }); + try { + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + callbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch (err: any) { + expect(err.message).toEqual( + 'Failed to retrieve signing key with error: Error: Error: error' + ); + } }); it('returns error if callbackContext.authresult has an error property filled', async () => { @@ -310,19 +315,19 @@ expect(result).toEqual({ }, ]; - service - .callbackHistoryAndResetJwtKeys( - callbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: (err) => { - expect(err.message).toEqual( - 'AuthCallback AuthResult came with error: someError' - ); - }, - }); + try { + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + callbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch (err: any) { + expect(err.message).toEqual( + 'AuthCallback AuthResult came with error: someError' + ); + } }); it('calls resetAuthorizationData, resets nonce and authStateService in case of an error', async () => { @@ -347,25 +352,23 @@ expect(result).toEqual({ 'updateAndPublishAuthState' ); - service - .callbackHistoryAndResetJwtKeys( - callbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: () => { - expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); - expect(setNonceSpy).toHaveBeenCalledTimes(1); - expect( - updateAndPublishAuthStateSpy - ).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: false, - validationResult: ValidationResult.SecureTokenServerError, - isRenewProcess: false, - }); - }, + try { + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + callbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch { + expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); + expect(setNonceSpy).toHaveBeenCalledTimes(1); + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: false, + validationResult: ValidationResult.SecureTokenServerError, + isRenewProcess: false, }); + } }); it('calls authStateService.updateAndPublishAuthState with login required if the error is `login_required`', async () => { @@ -390,25 +393,23 @@ expect(result).toEqual({ 'updateAndPublishAuthState' ); - service - .callbackHistoryAndResetJwtKeys( - callbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: () => { - expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); - expect(setNonceSpy).toHaveBeenCalledTimes(1); - expect( - updateAndPublishAuthStateSpy - ).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: false, - validationResult: ValidationResult.LoginRequired, - isRenewProcess: false, - }); - }, + try { + await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + callbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch { + expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); + expect(setNonceSpy).toHaveBeenCalledTimes(1); + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: false, + validationResult: ValidationResult.LoginRequired, + isRenewProcess: false, }); + } }); it('should store jwtKeys', async () => { @@ -434,26 +435,23 @@ expect(result).toEqual({ of(DUMMY_JWT_KEYS) ); - service - .callbackHistoryAndResetJwtKeys( - initialCallbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - next: (callbackContext: CallbackContext) => { - expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); - expect(storagePersistenceServiceSpy).toBeCalledWith([ - ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], - ['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]], - ]); - - expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); - }, - error: (err) => { - expect(err).toBeFalsy(); - }, - }); + try { + const callbackContext: CallbackContext = await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + initialCallbackContext, + allConfigs[0]!, + allConfigs + ) + ); + expect(storagePersistenceServiceSpy).toHaveBeenCalledTimes(2); + expect(storagePersistenceServiceSpy).toBeCalledWith([ + ['authnResult', DUMMY_AUTH_RESULT, allConfigs[0]], + ['jwtKeys', DUMMY_JWT_KEYS, allConfigs[0]], + ]); + expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); + } catch (err: any) { + expect(err).toBeFalsy(); + } }); it('should not store jwtKeys on error', async () => { @@ -480,29 +478,23 @@ expect(result).toEqual({ throwError(() => new Error('Error')) ); - service - .callbackHistoryAndResetJwtKeys( - initialCallbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - next: (callbackContext: CallbackContext) => { - expect(callbackContext).toBeFalsy(); - }, - error: (err) => { - expect(err).toBeTruthy(); - - // storagePersistenceService.write() should not have been called with jwtKeys - expect( - storagePersistenceServiceSpy - ).toHaveBeenCalledExactlyOnceWith( - 'authnResult', - authResult, - allConfigs[0] - ); - }, - }); + try { + const callbackContext: CallbackContext = await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + initialCallbackContext, + allConfigs[0]!, + allConfigs + ) + ); + expect(callbackContext).toBeFalsy(); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( + 'authnResult', + authResult, + allConfigs[0] + ); + } }); it('should fallback to stored jwtKeys on error', async () => { @@ -530,23 +522,22 @@ expect(result).toEqual({ throwError(() => new Error('Error')) ); - service - .callbackHistoryAndResetJwtKeys( - initialCallbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - next: (callbackContext: CallbackContext) => { - expect( - storagePersistenceServiceSpy - ).toHaveBeenCalledExactlyOnceWith('jwtKeys', allConfigs[0]); - expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); - }, - error: (err) => { - expect(err).toBeFalsy(); - }, - }); + try { + const callbackContext: CallbackContext = await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + initialCallbackContext, + allConfigs[0]!, + allConfigs + ) + ); + expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( + 'jwtKeys', + allConfigs[0] + ); + expect(callbackContext.jwtKeys).toEqual(DUMMY_JWT_KEYS); + } catch (err: any) { + expect(err).toBeFalsy(); + } }); it('should throw error if no jwtKeys are stored', async () => { @@ -568,20 +559,18 @@ expect(result).toEqual({ throwError(() => new Error('Error')) ); - service - .callbackHistoryAndResetJwtKeys( - initialCallbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - next: (callbackContext: CallbackContext) => { - expect(callbackContext).toBeFalsy(); - }, - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + const callbackContext: CallbackContext = await lastValueFrom( + service.callbackHistoryAndResetJwtKeys( + initialCallbackContext, + allConfigs[0]!, + allConfigs + ) + ); + expect(callbackContext).toBeFalsy(); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); }); diff --git a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts index 3e826df..b24e87e 100644 --- a/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts +++ b/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts @@ -98,10 +98,10 @@ export class HistoryJwtKeysCallbackHandlerService { // fallback: try to load jwtKeys from storage const storedJwtKeys = this.readSigningKeys(config); - if (!!storedJwtKeys) { + if (storedJwtKeys) { this.loggerService.logWarning( config, - `Failed to retrieve signing keys, fallback to stored keys` + 'Failed to retrieve signing keys, fallback to stored keys' ); return of(storedJwtKeys); @@ -116,7 +116,7 @@ export class HistoryJwtKeysCallbackHandlerService { return of(callbackContext); } - const errorMessage = `Failed to retrieve signing key`; + const errorMessage = 'Failed to retrieve signing key'; this.loggerService.logWarning(config, errorMessage); diff --git a/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts b/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts index 57b37b0..d7f9d11 100644 --- a/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/implicit-flow-callback-handler.service.spec.ts @@ -1,4 +1,5 @@ import { TestBed } from '@/testing'; +import { lastValueFrom } from 'rxjs'; import { vi } from 'vitest'; import { DOCUMENT } from '../../dom'; import { LoggerService } from '../../logging/logger.service'; @@ -57,9 +58,10 @@ describe('ImplicitFlowCallbackHandlerService', () => { }, ]; - await lastValueFrom(service - .implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')); -expect(resetAuthorizationDataSpy).toHaveBeenCalled(); + await lastValueFrom( + service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash') + ); + expect(resetAuthorizationDataSpy).toHaveBeenCalled(); }); it('does NOT calls "resetAuthorizationData" if silent renew is running', async () => { @@ -74,9 +76,10 @@ expect(resetAuthorizationDataSpy).toHaveBeenCalled(); }, ]; - await lastValueFrom(service - .implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')); -expect(resetAuthorizationDataSpy).not.toHaveBeenCalled(); + await lastValueFrom( + service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash') + ); + expect(resetAuthorizationDataSpy).not.toHaveBeenCalled(); }); it('returns callbackContext if all params are good', async () => { @@ -99,9 +102,10 @@ expect(resetAuthorizationDataSpy).not.toHaveBeenCalled(); }, ]; - const callbackContext = await lastValueFrom(service - .implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash')); -expect(callbackContext).toEqual(expectedCallbackContext); + const callbackContext = await lastValueFrom( + service.implicitFlowCallback(allConfigs[0]!, allConfigs, 'anyHash') + ); + expect(callbackContext).toEqual(expectedCallbackContext); }); it('uses window location hash if no hash is passed', async () => { @@ -124,9 +128,10 @@ expect(callbackContext).toEqual(expectedCallbackContext); }, ]; - const callbackContext = await lastValueFrom(service - .implicitFlowCallback(allConfigs[0]!, allConfigs)); -expect(callbackContext).toEqual(expectedCallbackContext); + const callbackContext = await lastValueFrom( + service.implicitFlowCallback(allConfigs[0]!, allConfigs) + ); + expect(callbackContext).toEqual(expectedCallbackContext); }); }); }); diff --git a/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts b/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts index 7ae7789..46188d0 100644 --- a/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/refresh-session-callback-handler.service.spec.ts @@ -1,4 +1,5 @@ import { TestBed } from '@/testing'; +import { lastValueFrom } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { LoggerService } from '../../logging/logger.service'; @@ -21,9 +22,6 @@ describe('RefreshSessionCallbackHandlerService', () => { mockProvider(FlowsDataService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(RefreshSessionCallbackHandlerService); flowsDataService = TestBed.inject(FlowsDataService); authStateService = TestBed.inject(AuthStateService); @@ -56,9 +54,10 @@ describe('RefreshSessionCallbackHandlerService', () => { existingIdToken: 'henlo-legger', } as CallbackContext; - const callbackContext = await lastValueFrom(service - .refreshSessionWithRefreshTokens({ configId: 'configId1' })); -expect(callbackContext).toEqual(expectedCallbackContext); + const callbackContext = await lastValueFrom( + service.refreshSessionWithRefreshTokens({ configId: 'configId1' }) + ); + expect(callbackContext).toEqual(expectedCallbackContext); }); it('throws error if no refresh token is given', async () => { @@ -69,13 +68,13 @@ expect(callbackContext).toEqual(expectedCallbackContext); vi.spyOn(authStateService, 'getRefreshToken').mockReturnValue(''); vi.spyOn(authStateService, 'getIdToken').mockReturnValue('henlo-legger'); - service - .refreshSessionWithRefreshTokens({ configId: 'configId1' }) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.refreshSessionWithRefreshTokens({ configId: 'configId1' }) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); }); }); diff --git a/src/flows/callback-handling/refresh-session-callback-handler.service.ts b/src/flows/callback-handling/refresh-session-callback-handler.service.ts index 075f1f3..766eaf2 100644 --- a/src/flows/callback-handling/refresh-session-callback-handler.service.ts +++ b/src/flows/callback-handling/refresh-session-callback-handler.service.ts @@ -1,10 +1,10 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of, throwError } from 'rxjs'; import { AuthStateService } from '../../auth-state/auth-state.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { TokenValidationService } from '../../validation/token-validation.service'; -import { CallbackContext } from '../callback-context'; +import type { CallbackContext } from '../callback-context'; import { FlowsDataService } from '../flows-data.service'; @Injectable() @@ -24,7 +24,7 @@ export class RefreshSessionCallbackHandlerService { this.loggerService.logDebug( config, - 'RefreshSession created. Adding myautostate: ' + stateData + `RefreshSession created. Adding myautostate: ${stateData}` ); const refreshToken = this.authStateService.getRefreshToken(config); const idToken = this.authStateService.getIdToken(config); @@ -53,12 +53,11 @@ export class RefreshSessionCallbackHandlerService { ); return of(callbackContext); - } else { - const errorMessage = 'no refresh token found, please login'; - - this.loggerService.logError(config, errorMessage); - - return throwError(() => new Error(errorMessage)); } + const errorMessage = 'no refresh token found, please login'; + + this.loggerService.logError(config, errorMessage); + + return throwError(() => new Error(errorMessage)); } } diff --git a/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts b/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts index ad7b648..f01dcab 100644 --- a/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/refresh-token-callback-handler.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { HttpErrorResponse, HttpHeaders } from '@ngify/http'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; @@ -26,9 +26,6 @@ describe('RefreshTokenCallbackHandlerService', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(RefreshTokenCallbackHandlerService); storagePersistenceService = TestBed.inject(StoragePersistenceService); dataService = TestBed.inject(DataService); @@ -48,13 +45,13 @@ describe('RefreshTokenCallbackHandlerService', () => { }); it('throws error if no tokenEndpoint is given', async () => { - (service as any) - .refreshTokensRequestTokens({} as CallbackContext) - .subscribe({ - error: (err: unknown) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + (service as any).refreshTokensRequestTokens({} as CallbackContext) + ); + } catch (err: unknown) { + expect(err).toBeTruthy(); + } }); it('calls data service if all params are good', async () => { @@ -66,21 +63,22 @@ describe('RefreshTokenCallbackHandlerService', () => { () => ({ tokenEndpoint: 'tokenEndpoint' }) ); - await lastValueFrom(service - .refreshTokensRequestTokens({} as CallbackContext, { + await lastValueFrom( + service.refreshTokensRequestTokens({} as CallbackContext, { configId: 'configId1', - })); -expect(postSpy).toHaveBeenCalledExactlyOnceWith( - 'tokenEndpoint', - undefined, - { configId: 'configId1' }, - expect.any(HttpHeaders) - );; -const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; -expect(httpHeaders.has('Content-Type')).toBeTruthy();; -expect(httpHeaders.get('Content-Type')).toBe( - 'application/x-www-form-urlencoded' - ); + }) + ); + expect(postSpy).toHaveBeenCalledExactlyOnceWith( + 'tokenEndpoint', + undefined, + { configId: 'configId1' }, + expect.any(HttpHeaders) + ); + const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTruthy(); + expect(httpHeaders.get('Content-Type')).toBe( + 'application/x-www-form-urlencoded' + ); }); it('calls data service with correct headers if all params are good', async () => { @@ -92,15 +90,16 @@ expect(httpHeaders.get('Content-Type')).toBe( () => ({ tokenEndpoint: 'tokenEndpoint' }) ); - await lastValueFrom(service - .refreshTokensRequestTokens({} as CallbackContext, { + await lastValueFrom( + service.refreshTokensRequestTokens({} as CallbackContext, { configId: 'configId1', - })); -const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; -expect(httpHeaders.has('Content-Type')).toBeTruthy();; -expect(httpHeaders.get('Content-Type')).toBe( - 'application/x-www-form-urlencoded' - ); + }) + ); + const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTruthy(); + expect(httpHeaders.get('Content-Type')).toBe( + 'application/x-www-form-urlencoded' + ); }); it('returns error in case of http error', async () => { @@ -115,13 +114,13 @@ expect(httpHeaders.get('Content-Type')).toBe( () => ({ tokenEndpoint: 'tokenEndpoint' }) ); - service - .refreshTokensRequestTokens({} as CallbackContext, config) - .subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom( + service.refreshTokensRequestTokens({} as CallbackContext, config) + ); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('retries request in case of no connection http error and succeeds', async () => { @@ -139,18 +138,15 @@ expect(httpHeaders.get('Content-Type')).toBe( () => ({ tokenEndpoint: 'tokenEndpoint' }) ); - service - .refreshTokensRequestTokens({} as CallbackContext, config) - .subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(postSpy).toHaveBeenCalledTimes(1); - }, - error: (err) => { - // fails if there should be a result - expect(err).toBeFalsy(); - }, - }); + try { + const res = await lastValueFrom( + service.refreshTokensRequestTokens({} as CallbackContext, config) + ); + expect(res).toBeTruthy(); + expect(postSpy).toHaveBeenCalledTimes(1); + } catch (err: any) { + expect(err).toBeFalsy(); + } }); it('retries request in case of no connection http error and fails because of http error afterwards', async () => { @@ -168,18 +164,15 @@ expect(httpHeaders.get('Content-Type')).toBe( () => ({ tokenEndpoint: 'tokenEndpoint' }) ); - service - .refreshTokensRequestTokens({} as CallbackContext, config) - .subscribe({ - next: (res) => { - // fails if there should be a result - expect(res).toBeFalsy(); - }, - error: (err) => { - expect(err).toBeTruthy(); - expect(postSpy).toHaveBeenCalledTimes(1); - }, - }); + try { + const res = await lastValueFrom( + service.refreshTokensRequestTokens({} as CallbackContext, config) + ); + expect(res).toBeFalsy(); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(postSpy).toHaveBeenCalledTimes(1); + } }); }); }); diff --git a/src/flows/callback-handling/refresh-token-callback-handler.service.ts b/src/flows/callback-handling/refresh-token-callback-handler.service.ts index df786f6..7fb59e5 100644 --- a/src/flows/callback-handling/refresh-token-callback-handler.service.ts +++ b/src/flows/callback-handling/refresh-token-callback-handler.service.ts @@ -1,13 +1,13 @@ import { HttpHeaders } from '@ngify/http'; import { inject, Injectable } from 'injection-js'; -import { Observable, of, throwError, timer } from 'rxjs'; +import { type Observable, of, throwError, timer } from 'rxjs'; import { catchError, mergeMap, retryWhen, switchMap } from 'rxjs/operators'; import { DataService } from '../../api/data.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { UrlService } from '../../utils/url/url.service'; -import { AuthResult, CallbackContext } from '../callback-context'; +import type { AuthResult, CallbackContext } from '../callback-context'; import { isNetworkError } from './error-helper'; @Injectable() diff --git a/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts b/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts index 7913d80..7bdaf34 100644 --- a/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/state-validation-callback-handler.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@/testing'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { DOCUMENT } from '../../dom'; @@ -42,9 +42,6 @@ describe('StateValidationCallbackHandlerService', () => { }, ], }); - }); - - beforeEach(() => { service = TestBed.inject(StateValidationCallbackHandlerService); stateValidationService = TestBed.inject(StateValidationService); loggerService = TestBed.inject(LoggerService); @@ -69,18 +66,19 @@ describe('StateValidationCallbackHandlerService', () => { ); const allConfigs = [{ configId: 'configId1' }]; - const newCallbackContext = await lastValueFrom(service - .callbackStateValidation( + const newCallbackContext = await lastValueFrom( + service.callbackStateValidation( {} as CallbackContext, allConfigs[0]!, allConfigs - )); -expect(newCallbackContext).toEqual({ - validationResult: { - idToken: 'idTokenJustForTesting', - authResponseIsValid: true, - }, - } as CallbackContext); + ) + ); + expect(newCallbackContext).toEqual({ + validationResult: { + idToken: 'idTokenJustForTesting', + authResponseIsValid: true, + }, + } as CallbackContext); }); it('logs error in case of an error', async () => { @@ -96,20 +94,20 @@ expect(newCallbackContext).toEqual({ const loggerSpy = vi.spyOn(loggerService, 'logWarning'); const allConfigs = [{ configId: 'configId1' }]; - service - .callbackStateValidation( - {} as CallbackContext, + try { + await lastValueFrom( + service.callbackStateValidation( + {} as CallbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch { + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: () => { - expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( - allConfigs[0]!, - 'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash' - ); - }, - }); + 'authorizedCallback, token(s) validation failed, resetting. Hash: &anyFakeHash' + ); + } }); it('calls resetAuthDataService.resetAuthorizationData and authStateService.updateAndPublishAuthState in case of an error', async () => { @@ -133,24 +131,22 @@ expect(newCallbackContext).toEqual({ ); const allConfigs = [{ configId: 'configId1' }]; - service - .callbackStateValidation( - { isRenewProcess: true } as CallbackContext, - allConfigs[0]!, - allConfigs - ) - .subscribe({ - error: () => { - expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); - expect( - updateAndPublishAuthStateSpy - ).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: false, - validationResult: ValidationResult.LoginRequired, - isRenewProcess: true, - }); - }, + try { + await lastValueFrom( + service.callbackStateValidation( + { isRenewProcess: true } as CallbackContext, + allConfigs[0]!, + allConfigs + ) + ); + } catch { + expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: false, + validationResult: ValidationResult.LoginRequired, + isRenewProcess: true, }); + } }); }); }); diff --git a/src/flows/callback-handling/state-validation-callback-handler.service.ts b/src/flows/callback-handling/state-validation-callback-handler.service.ts index 8498455..a2362bf 100644 --- a/src/flows/callback-handling/state-validation-callback-handler.service.ts +++ b/src/flows/callback-handling/state-validation-callback-handler.service.ts @@ -43,21 +43,20 @@ export class StateValidationCallbackHandlerService { ); return callbackContext; - } else { - const errorMessage = `authorizedCallback, token(s) validation failed, resetting. Hash: ${this.document.location.hash}`; - - this.loggerService.logWarning(configuration, errorMessage); - this.resetAuthDataService.resetAuthorizationData( - configuration, - allConfigs - ); - this.publishUnauthorizedState( - callbackContext.validationResult, - callbackContext.isRenewProcess - ); - - throw new Error(errorMessage); } + const errorMessage = `authorizedCallback, token(s) validation failed, resetting. Hash: ${this.document.location.hash}`; + + this.loggerService.logWarning(configuration, errorMessage); + this.resetAuthDataService.resetAuthorizationData( + configuration, + allConfigs + ); + this.publishUnauthorizedState( + callbackContext.validationResult, + callbackContext.isRenewProcess + ); + + throw new Error(errorMessage); }) ); } diff --git a/src/flows/callback-handling/user-callback-handler.service.spec.ts b/src/flows/callback-handling/user-callback-handler.service.spec.ts index 12bdc22..3531a7b 100644 --- a/src/flows/callback-handling/user-callback-handler.service.spec.ts +++ b/src/flows/callback-handling/user-callback-handler.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@/testing'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../../auth-state/auth-state.service'; import { LoggerService } from '../../logging/logger.service'; @@ -30,9 +30,6 @@ describe('UserCallbackHandlerService', () => { mockProvider(ResetAuthDataService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(UserCallbackHandlerService); flowsDataService = TestBed.inject(FlowsDataService); authStateService = TestBed.inject(AuthStateService); @@ -73,10 +70,11 @@ describe('UserCallbackHandlerService', () => { const spy = vi.spyOn(flowsDataService, 'setSessionState'); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]);; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith('mystate', allConfigs[0]); + expect(resultCallbackContext).toEqual(callbackContext); }); it('does NOT call flowsDataService.setSessionState if autoUserInfo is false, isRenewProcess is true and refreshToken is null', async () => { @@ -105,10 +103,11 @@ expect(resultCallbackContext).toEqual(callbackContext); ]; const spy = vi.spyOn(flowsDataService, 'setSessionState'); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(spy).not.toHaveBeenCalled();; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(spy).not.toHaveBeenCalled(); + expect(resultCallbackContext).toEqual(callbackContext); }); it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value', async () => { @@ -137,10 +136,11 @@ expect(resultCallbackContext).toEqual(callbackContext); ]; const spy = vi.spyOn(flowsDataService, 'setSessionState'); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(spy).not.toHaveBeenCalled();; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(spy).not.toHaveBeenCalled(); + expect(resultCallbackContext).toEqual(callbackContext); }); it('does NOT call flowsDataService.setSessionState if autoUserInfo is false isRenewProcess is false, refreshToken has value, id_token is false', async () => { @@ -165,10 +165,11 @@ expect(resultCallbackContext).toEqual(callbackContext); const spy = vi.spyOn(flowsDataService, 'setSessionState'); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(spy).not.toHaveBeenCalled();; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(spy).not.toHaveBeenCalled(); + expect(resultCallbackContext).toEqual(callbackContext); }); it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is false', async () => { @@ -202,14 +203,15 @@ expect(resultCallbackContext).toEqual(callbackContext); 'updateAndPublishAuthState' ); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: true, - validationResult: ValidationResult.NotSet, - isRenewProcess: false, - });; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: true, + validationResult: ValidationResult.NotSet, + isRenewProcess: false, + }); + expect(resultCallbackContext).toEqual(callbackContext); }); it('calls userService.getAndPersistUserDataInStore with correct params if autoUserInfo is true', async () => { @@ -242,18 +244,17 @@ expect(resultCallbackContext).toEqual(callbackContext); .spyOn(userService, 'getAndPersistUserDataInStore') .mockReturnValue(of({ user: 'some_data' })); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect( - getAndPersistUserDataInStoreSpy - ).toHaveBeenCalledExactlyOnceWith( - allConfigs[0]!, - allConfigs, - false, - 'idtoken', - 'decoded' - );; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(getAndPersistUserDataInStoreSpy).toHaveBeenCalledExactlyOnceWith( + allConfigs[0]!, + allConfigs, + false, + 'idtoken', + 'decoded' + ); + expect(resultCallbackContext).toEqual(callbackContext); }); it('calls authStateService.updateAndPublishAuthState with correct params if autoUserInfo is true', async () => { @@ -291,14 +292,15 @@ expect(resultCallbackContext).toEqual(callbackContext); 'updateAndPublishAuthState' ); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: true, - validationResult: ValidationResult.MaxOffsetExpired, - isRenewProcess: false, - });; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: true, + validationResult: ValidationResult.MaxOffsetExpired, + isRenewProcess: false, + }); + expect(resultCallbackContext).toEqual(callbackContext); }); it('calls flowsDataService.setSessionState with correct params if user data is present and NOT refresh token', async () => { @@ -333,13 +335,14 @@ expect(resultCallbackContext).toEqual(callbackContext); ); const setSessionStateSpy = vi.spyOn(flowsDataService, 'setSessionState'); - const resultCallbackContext = await lastValueFrom(service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs)); -expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith( - 'mystate', - allConfigs[0] - );; -expect(resultCallbackContext).toEqual(callbackContext); + const resultCallbackContext = await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + expect(setSessionStateSpy).toHaveBeenCalledExactlyOnceWith( + 'mystate', + allConfigs[0] + ); + expect(resultCallbackContext).toEqual(callbackContext); }); it('calls authStateService.publishUnauthorizedState with correct params if user info which are coming back are null', async () => { @@ -377,22 +380,20 @@ expect(resultCallbackContext).toEqual(callbackContext); 'updateAndPublishAuthState' ); - service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs) - .subscribe({ - error: (err) => { - expect( - updateAndPublishAuthStateSpy - ).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: false, - validationResult: ValidationResult.MaxOffsetExpired, - isRenewProcess: false, - }); - expect(err.message).toEqual( - 'Failed to retrieve user info with error: Error: Called for userData but they were null' - ); - }, + try { + await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + } catch (err: any) { + expect(updateAndPublishAuthStateSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: false, + validationResult: ValidationResult.MaxOffsetExpired, + isRenewProcess: false, }); + expect(err.message).toEqual( + 'Failed to retrieve user info with error: Error: Called for userData but they were null' + ); + } }); it('calls resetAuthDataService.resetAuthorizationData if user info which are coming back are null', async () => { @@ -430,16 +431,16 @@ expect(resultCallbackContext).toEqual(callbackContext); 'resetAuthorizationData' ); - service - .callbackUser(callbackContext, allConfigs[0]!, allConfigs) - .subscribe({ - error: (err) => { - expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); - expect(err.message).toEqual( - 'Failed to retrieve user info with error: Error: Called for userData but they were null' - ); - }, - }); + try { + await lastValueFrom( + service.callbackUser(callbackContext, allConfigs[0]!, allConfigs) + ); + } catch (err: any) { + expect(resetAuthorizationDataSpy).toHaveBeenCalledTimes(1); + expect(err.message).toEqual( + 'Failed to retrieve user info with error: Error: Called for userData but they were null' + ); + } }); }); }); diff --git a/src/flows/callback-handling/user-callback-handler.service.ts b/src/flows/callback-handling/user-callback-handler.service.ts index 09730d1..5a312cb 100644 --- a/src/flows/callback-handling/user-callback-handler.service.ts +++ b/src/flows/callback-handling/user-callback-handler.service.ts @@ -35,6 +35,7 @@ export class UserCallbackHandlerService { if (!autoUserInfo) { if (!isRenewProcess || renewUserInfoAfterTokenRenew) { // userData is set to the id_token decoded, auto get user data set to false + // biome-ignore lint/nursery/useCollapsedIf: if (validationResult?.decodedIdToken) { this.userService.setUserDataToStore( validationResult.decodedIdToken, @@ -66,7 +67,7 @@ export class UserCallbackHandlerService { ) .pipe( switchMap((userData) => { - if (!!userData) { + if (userData) { if (!refreshToken) { this.flowsDataService.setSessionState( authResult?.session_state, @@ -77,18 +78,17 @@ export class UserCallbackHandlerService { this.publishAuthState(validationResult, isRenewProcess); return of(callbackContext); - } else { - this.resetAuthDataService.resetAuthorizationData( - configuration, - allConfigs - ); - this.publishUnauthenticatedState(validationResult, isRenewProcess); - const errorMessage = `Called for userData but they were ${userData}`; - - this.loggerService.logWarning(configuration, errorMessage); - - return throwError(() => new Error(errorMessage)); } + this.resetAuthDataService.resetAuthorizationData( + configuration, + allConfigs + ); + this.publishUnauthenticatedState(validationResult, isRenewProcess); + const errorMessage = `Called for userData but they were ${userData}`; + + this.loggerService.logWarning(configuration, errorMessage); + + return throwError(() => new Error(errorMessage)); }), catchError((err) => { const errorMessage = `Failed to retrieve user info with error: ${err}`; diff --git a/src/flows/flows-data.service.spec.ts b/src/flows/flows-data.service.spec.ts index e5aa45f..fba5495 100644 --- a/src/flows/flows-data.service.spec.ts +++ b/src/flows/flows-data.service.spec.ts @@ -1,4 +1,4 @@ -import { TestBed } from '@/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; @@ -21,15 +21,13 @@ describe('Flows Data Service', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(FlowsDataService); storagePersistenceService = TestBed.inject(StoragePersistenceService); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { - jasmine.clock().uninstall(); + vi.useRealTimers(); }); it('should create', () => { @@ -141,10 +139,11 @@ describe('Flows Data Service', () => { describe('codeVerifier', () => { it('getCodeVerifier returns value from the store', () => { - const spy = vi - .spyOn(storagePersistenceService, 'read') - .withArgs('codeVerifier', { configId: 'configId1' }) - .mockReturnValue('Genesis'); + const spy = mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['codeVerifier', { configId: 'configId1' }], + () => 'Genesis' + ); const result = service.getCodeVerifier({ configId: 'configId1' }); @@ -173,11 +172,12 @@ describe('Flows Data Service', () => { configId: 'configId1', }; - jasmine.clock().uninstall(); - jasmine.clock().install(); + vi.useRealTimers(); + vi.useFakeTimers(); + const baseTime = new Date(); - jasmine.clock().mockDate(baseTime); + vi.setSystemTime(baseTime); mockImplementationWhenArgsEqual( vi.spyOn(storagePersistenceService, 'read'), @@ -212,11 +212,11 @@ describe('Flows Data Service', () => { describe('setCodeFlowInProgress', () => { it('set setCodeFlowInProgress to `in progress` when called', () => { - jasmine.clock().uninstall(); - jasmine.clock().install(); + vi.useRealTimers(); + vi.useFakeTimers(); const baseTime = new Date(); - jasmine.clock().mockDate(baseTime); + vi.setSystemTime(baseTime); const spy = vi.spyOn(storagePersistenceService, 'write'); @@ -253,23 +253,27 @@ describe('Flows Data Service', () => { configId: 'configId1', }; - jasmine.clock().uninstall(); - jasmine.clock().install(); + vi.useRealTimers(); + vi.useFakeTimers(); const baseTime = new Date(); - jasmine.clock().mockDate(baseTime); + vi.setSystemTime(baseTime); const storageObject = { state: 'running', dateOfLaunchedProcessUtc: baseTime.toISOString(), }; - vi.spyOn(storagePersistenceService, 'read') - .withArgs('storageSilentRenewRunning', config) - .mockReturnValue(JSON.stringify(storageObject)); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['storageSilentRenewRunning', config], + () => JSON.stringify(storageObject) + ); const spyWrite = vi.spyOn(storagePersistenceService, 'write'); - jasmine.clock().tick((config.silentRenewTimeoutInSeconds + 1) * 1000); + vi.advanceTimersByTimeAsync( + (config.silentRenewTimeoutInSeconds + 1) * 1000 + ); const isSilentRenewRunningResult = service.isSilentRenewRunning(config); @@ -287,20 +291,22 @@ describe('Flows Data Service', () => { configId: 'configId1', }; - jasmine.clock().uninstall(); - jasmine.clock().install(); + vi.useRealTimers(); + vi.useFakeTimers(); const baseTime = new Date(); - jasmine.clock().mockDate(baseTime); + vi.setSystemTime(baseTime); const storageObject = { state: 'running', dateOfLaunchedProcessUtc: baseTime.toISOString(), }; - vi.spyOn(storagePersistenceService, 'read') - .withArgs('storageSilentRenewRunning', config) - .mockReturnValue(JSON.stringify(storageObject)); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['storageSilentRenewRunning', config], + () => JSON.stringify(storageObject) + ); const spyWrite = vi.spyOn(storagePersistenceService, 'write'); const isSilentRenewRunningResult = service.isSilentRenewRunning(config); @@ -326,11 +332,11 @@ describe('Flows Data Service', () => { describe('setSilentRenewRunning', () => { it('set setSilentRenewRunning to `running` with lauched time when called', () => { - jasmine.clock().uninstall(); - jasmine.clock().install(); + vi.useRealTimers(); + vi.useFakeTimers(); const baseTime = new Date(); - jasmine.clock().mockDate(baseTime); + vi.setSystemTime(baseTime); const storageObject = { state: 'running', diff --git a/src/flows/flows.service.spec.ts b/src/flows/flows.service.spec.ts index d08c6f7..a7b36c1 100644 --- a/src/flows/flows.service.spec.ts +++ b/src/flows/flows.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@/testing'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { mockProvider } from '../testing/mock'; import type { CallbackContext } from './callback-context'; @@ -35,9 +35,6 @@ describe('Flows Service', () => { mockProvider(RefreshTokenCallbackHandlerService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(FlowsService); codeFlowCallbackHandlerService = TestBed.inject( CodeFlowCallbackHandlerService @@ -90,17 +87,22 @@ describe('Flows Service', () => { }, ]; - const value = await lastValueFrom(service - .processCodeFlowCallback('some-url1234', allConfigs[0]!, allConfigs)); -expect(value).toEqual({} as CallbackContext);; -expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith( - 'some-url1234', - allConfigs[0] - );; -expect(codeFlowCodeRequestSpy).toHaveBeenCalledTimes(1);; -expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalledTimes(1);; -expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1);; -expect(callbackUserSpy).toHaveBeenCalledTimes(1); + const value = await lastValueFrom( + service.processCodeFlowCallback( + 'some-url1234', + allConfigs[0]!, + allConfigs + ) + ); + expect(value).toEqual({} as CallbackContext); + expect(codeFlowCallbackSpy).toHaveBeenCalledExactlyOnceWith( + 'some-url1234', + allConfigs[0] + ); + expect(codeFlowCodeRequestSpy).toHaveBeenCalledTimes(1); + expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalledTimes(1); + expect(callbackStateValidationSpy).toHaveBeenCalledTimes(1); + expect(callbackUserSpy).toHaveBeenCalledTimes(1); }); }); @@ -127,17 +129,18 @@ expect(callbackUserSpy).toHaveBeenCalledTimes(1); }, ]; - const value = await lastValueFrom(service - .processSilentRenewCodeFlowCallback( + const value = await lastValueFrom( + service.processSilentRenewCodeFlowCallback( {} as CallbackContext, allConfigs[0]!, allConfigs - )); -expect(value).toEqual({} as CallbackContext);; -expect(codeFlowCodeRequestSpy).toHaveBeenCalled();; -expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();; -expect(callbackStateValidationSpy).toHaveBeenCalled();; -expect(callbackUserSpy).toHaveBeenCalled(); + ) + ); + expect(value).toEqual({} as CallbackContext); + expect(codeFlowCodeRequestSpy).toHaveBeenCalled(); + expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled(); + expect(callbackStateValidationSpy).toHaveBeenCalled(); + expect(callbackUserSpy).toHaveBeenCalled(); }); }); @@ -164,13 +167,18 @@ expect(callbackUserSpy).toHaveBeenCalled(); }, ]; - const value = await lastValueFrom(service - .processImplicitFlowCallback(allConfigs[0]!, allConfigs, 'any-hash')); -expect(value).toEqual({} as CallbackContext);; -expect(implicitFlowCallbackSpy).toHaveBeenCalled();; -expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();; -expect(callbackStateValidationSpy).toHaveBeenCalled();; -expect(callbackUserSpy).toHaveBeenCalled(); + const value = await lastValueFrom( + service.processImplicitFlowCallback( + allConfigs[0]!, + allConfigs, + 'any-hash' + ) + ); + expect(value).toEqual({} as CallbackContext); + expect(implicitFlowCallbackSpy).toHaveBeenCalled(); + expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled(); + expect(callbackStateValidationSpy).toHaveBeenCalled(); + expect(callbackUserSpy).toHaveBeenCalled(); }); }); @@ -203,14 +211,15 @@ expect(callbackUserSpy).toHaveBeenCalled(); }, ]; - const value = await lastValueFrom(service - .processRefreshToken(allConfigs[0]!, allConfigs)); -expect(value).toEqual({} as CallbackContext);; -expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled();; -expect(refreshTokensRequestTokensSpy).toHaveBeenCalled();; -expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled();; -expect(callbackStateValidationSpy).toHaveBeenCalled();; -expect(callbackUserSpy).toHaveBeenCalled(); + const value = await lastValueFrom( + service.processRefreshToken(allConfigs[0]!, allConfigs) + ); + expect(value).toEqual({} as CallbackContext); + expect(refreshSessionWithRefreshTokensSpy).toHaveBeenCalled(); + expect(refreshTokensRequestTokensSpy).toHaveBeenCalled(); + expect(callbackHistoryAndResetJwtKeysSpy).toHaveBeenCalled(); + expect(callbackStateValidationSpy).toHaveBeenCalled(); + expect(callbackUserSpy).toHaveBeenCalled(); }); }); }); diff --git a/src/flows/random/random.service.spec.ts b/src/flows/random/random.service.spec.ts index 1660b4e..b99bf34 100644 --- a/src/flows/random/random.service.spec.ts +++ b/src/flows/random/random.service.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; import { LoggerService } from '../../logging/logger.service'; import { mockProvider } from '../../testing/mock'; import { CryptoService } from '../../utils/crypto/crypto.service'; @@ -12,9 +11,6 @@ describe('RandomService Tests', () => { TestBed.configureTestingModule({ providers: [RandomService, mockProvider(LoggerService), CryptoService], }); - }); - - beforeEach(() => { randomService = TestBed.inject(RandomService); }); diff --git a/src/flows/random/random.service.ts b/src/flows/random/random.service.ts index 37a3cf2..86b7e14 100644 --- a/src/flows/random/random.service.ts +++ b/src/flows/random/random.service.ts @@ -1,5 +1,5 @@ -import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import { Injectable, inject } from 'injection-js'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { CryptoService } from '../../utils/crypto/crypto.service'; @@ -37,7 +37,7 @@ export class RandomService { } private toHex(dec: number): string { - return ('0' + dec.toString(16)).substr(-2); + return `0${dec.toString(16)}`.substr(-2); } private randomString(length: number): string { diff --git a/src/flows/reset-auth-data.service.spec.ts b/src/flows/reset-auth-data.service.spec.ts index 7de2d26..d3342a7 100644 --- a/src/flows/reset-auth-data.service.spec.ts +++ b/src/flows/reset-auth-data.service.spec.ts @@ -23,9 +23,6 @@ describe('ResetAuthDataService', () => { mockProvider(LoggerService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(ResetAuthDataService); userService = TestBed.inject(UserService); flowsDataService = TestBed.inject(FlowsDataService); diff --git a/src/flows/signin-key-data.service.spec.ts b/src/flows/signin-key-data.service.spec.ts index 9193722..9ffdcbb 100644 --- a/src/flows/signin-key-data.service.spec.ts +++ b/src/flows/signin-key-data.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { HttpResponse } from '@ngify/http'; -import { isObservable, of, throwError } from 'rxjs'; +import { EmptyError, isObservable, lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { DataService } from '../api/data.service'; import { LoggerService } from '../logging/logger.service'; @@ -40,9 +40,6 @@ describe('Signin Key Data Service', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(SigninKeyDataService); storagePersistenceService = TestBed.inject(StoragePersistenceService); dataService = TestBed.inject(DataService); @@ -62,11 +59,11 @@ describe('Signin Key Data Service', () => { ); const result = service.getSigningKeys({ configId: 'configId1' }); - result.subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(result); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('throws error when no jwksUri given', async () => { @@ -77,11 +74,11 @@ describe('Signin Key Data Service', () => { ); const result = service.getSigningKeys({ configId: 'configId1' }); - result.subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(result); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); it('calls dataservice if jwksurl is given', async () => { @@ -94,13 +91,15 @@ describe('Signin Key Data Service', () => { const result = service.getSigningKeys({ configId: 'configId1' }); - result.subscribe({ - complete: () => { + try { + await lastValueFrom(result); + } catch (err: any) { + if (err instanceof EmptyError) { expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl', { configId: 'configId1', }); - }, - }); + } + } }); it('should retry once', async () => { @@ -116,12 +115,11 @@ describe('Signin Key Data Service', () => { ) ); - service.getSigningKeys({ configId: 'configId1' }).subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(res).toEqual(DUMMY_JWKS); - }, - }); + const res = await lastValueFrom( + service.getSigningKeys({ configId: 'configId1' }) + ); + expect(res).toBeTruthy(); + expect(res).toEqual(DUMMY_JWKS); }); it('should retry twice', async () => { @@ -138,12 +136,11 @@ describe('Signin Key Data Service', () => { ) ); - service.getSigningKeys({ configId: 'configId1' }).subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(res).toEqual(DUMMY_JWKS); - }, - }); + const res = await lastValueFrom( + service.getSigningKeys({ configId: 'configId1' }) + ); + expect(res).toBeTruthy(); + expect(res).toEqual(DUMMY_JWKS); }); it('should fail after three tries', async () => { @@ -161,16 +158,16 @@ describe('Signin Key Data Service', () => { ) ); - service.getSigningKeys({ configId: 'configId1' }).subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(service.getSigningKeys({ configId: 'configId1' })); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); }); describe('handleErrorGetSigningKeys', () => { - it('keeps observable if error is catched', async () => { + it('keeps observable if error is catched', () => { const result = (service as any).handleErrorGetSigningKeys( new HttpResponse() ); @@ -182,52 +179,54 @@ describe('Signin Key Data Service', () => { it('logs error if error is response', async () => { const logSpy = vi.spyOn(loggerService, 'logError'); - (service as any) - .handleErrorGetSigningKeys( - new HttpResponse({ status: 400, statusText: 'nono' }), - { configId: 'configId1' } - ) - .subscribe({ - error: () => { - expect(logSpy).toHaveBeenCalledExactlyOnceWith( - { configId: 'configId1' }, - '400 - nono {}' - ); - }, - }); + try { + await lastValueFrom( + (service as any).handleErrorGetSigningKeys( + new HttpResponse({ status: 400, statusText: 'nono' }), + { configId: 'configId1' } + ) + ); + } catch { + expect(logSpy).toHaveBeenCalledExactlyOnceWith( + { configId: 'configId1' }, + '400 - nono {}' + ); + } }); it('logs error if error is not a response', async () => { const logSpy = vi.spyOn(loggerService, 'logError'); - (service as any) - .handleErrorGetSigningKeys('Just some Error', { configId: 'configId1' }) - .subscribe({ - error: () => { - expect(logSpy).toHaveBeenCalledExactlyOnceWith( - { configId: 'configId1' }, - 'Just some Error' - ); - }, - }); + try { + await lastValueFrom( + (service as any).handleErrorGetSigningKeys('Just some Error', { + configId: 'configId1', + }) + ); + } catch { + expect(logSpy).toHaveBeenCalledExactlyOnceWith( + { configId: 'configId1' }, + 'Just some Error' + ); + } }); it('logs error if error with message property is not a response', async () => { const logSpy = vi.spyOn(loggerService, 'logError'); - (service as any) - .handleErrorGetSigningKeys( - { message: 'Just some Error' }, - { configId: 'configId1' } - ) - .subscribe({ - error: () => { - expect(logSpy).toHaveBeenCalledExactlyOnceWith( - { configId: 'configId1' }, - 'Just some Error' - ); - }, - }); + try { + await lastValueFrom( + (service as any).handleErrorGetSigningKeys( + { message: 'Just some Error' }, + { configId: 'configId1' } + ) + ); + } catch { + expect(logSpy).toHaveBeenCalledExactlyOnceWith( + { configId: 'configId1' }, + 'Just some Error' + ); + } }); }); }); diff --git a/src/flows/signin-key-data.service.ts b/src/flows/signin-key-data.service.ts index 58f287f..c18918f 100644 --- a/src/flows/signin-key-data.service.ts +++ b/src/flows/signin-key-data.service.ts @@ -1,12 +1,12 @@ import { HttpResponse } from '@ngify/http'; import { inject, Injectable } from 'injection-js'; -import { Observable, throwError } from 'rxjs'; +import { type Observable, throwError } from 'rxjs'; import { catchError, retry } from 'rxjs/operators'; import { DataService } from '../api/data.service'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; -import { JwtKeys } from '../validation/jwtkeys'; +import type { JwtKeys } from '../validation/jwtkeys'; @Injectable() export class SigninKeyDataService { @@ -62,7 +62,7 @@ export class SigninKeyDataService { } else { const { message } = errorResponse; - errMsg = !!message ? message : `${errorResponse}`; + errMsg = message ? message : `${errorResponse}`; } this.loggerService.logError(currentConfiguration, errMsg); diff --git a/src/iframe/check-session.service.spec.ts b/src/iframe/check-session.service.spec.ts index 1178241..9ab3a8c 100644 --- a/src/iframe/check-session.service.spec.ts +++ b/src/iframe/check-session.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { skip } from 'rxjs/operators'; import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; @@ -35,15 +35,13 @@ describe('CheckSessionService', () => { ), ], }); - }); - - beforeEach(() => { checkSessionService = TestBed.inject(CheckSessionService); loggerService = TestBed.inject(LoggerService); iFrameService = TestBed.inject(IFrameService); storagePersistenceService = TestBed.inject(StoragePersistenceService); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { const iFrameIdwhichshouldneverexist = window.document.getElementById( 'idwhichshouldneverexist' @@ -68,7 +66,7 @@ describe('CheckSessionService', () => { }); it('getOrCreateIframe calls iFrameService.addIFrameToWindowBody if no Iframe exists', () => { - vi.spyOn(iFrameService, 'addIFrameToWindowBody')(); + vi.spyOn(iFrameService, 'addIFrameToWindowBody'); const result = (checkSessionService as any).getOrCreateIframe({ configId: 'configId1', @@ -106,15 +104,18 @@ describe('CheckSessionService', () => { }); it('log warning if authWellKnownEndpoints.check_session_iframe is not existing', () => { - const spyLogWarning = vi.spyOn(loggerService, 'logWarning'); + const spyLogWarning = vi.spyOn(loggerService, 'logWarning'); const config = { configId: 'configId1' }; - vi.spyOn(loggerService, 'logDebug').mockImplementation( + vi.spyOn(loggerService, 'logDebug').mockImplementation( () => undefined ); - vi.spyOn(storagePersistenceService, 'read') - .withArgs('authWellKnownEndPoints', config) - .mockReturnValue({ checkSessionIframe: undefined }); + + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => ({ checkSessionIframe: undefined }) + ); (checkSessionService as any).init(config); expect(spyLogWarning).toHaveBeenCalledExactlyOnceWith( @@ -124,7 +125,7 @@ describe('CheckSessionService', () => { }); it('start() calls pollserversession() with clientId if no scheduledheartbeat is set', () => { - const spy = vi.spyOn(checkSessionService, 'pollServerSession'); + const spy = vi.spyOn(checkSessionService, 'pollServerSession'); const config = { clientId: 'clientId', configId: 'configId1' }; checkSessionService.start(config); @@ -133,7 +134,7 @@ describe('CheckSessionService', () => { it('start() does not call pollServerSession() if scheduledHeartBeatRunning is set', () => { const config = { configId: 'configId1' }; - const spy = vi.spyOn(checkSessionService, 'pollServerSession'); + const spy = vi.spyOn(checkSessionService, 'pollServerSession'); (checkSessionService as any).scheduledHeartBeatRunning = (): void => undefined; @@ -154,7 +155,10 @@ describe('CheckSessionService', () => { it('stopCheckingSession does nothing if scheduledHeartBeatRunning is not set', () => { (checkSessionService as any).scheduledHeartBeatRunning = null; - const spy = vi.spyOn(checkSessionService, 'clearScheduledHeartBeat'); + const spy = vi.spyOn( + checkSessionService, + 'clearScheduledHeartBeat' + ); checkSessionService.stop(); expect(spy).not.toHaveBeenCalledExactlyOnceWith(); @@ -187,11 +191,16 @@ describe('CheckSessionService', () => { describe('pollServerSession', () => { beforeEach(() => { - vi.spyOn(checkSessionService, 'init').mockReturnValue(of(undefined)); + vi.spyOn(checkSessionService, 'init').mockReturnValue( + of(undefined) + ); }); it('increases outstandingMessages', () => { - vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue({ + vi.spyOn( + checkSessionService, + 'getExistingIframe' + ).mockReturnValue({ contentWindow: { postMessage: () => undefined }, }); const authWellKnownEndpoints = { @@ -200,21 +209,25 @@ describe('CheckSessionService', () => { const config = { configId: 'configId1' }; mockImplementationWhenArgsEqual( - vi.spyOn(storagePersistenceService, 'read'), - ['authWellKnownEndPoints', config], - () => authWellKnownEndpoints - ) - .withArgs('session_state', config) - .mockReturnValue('session_state'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints + ), + ['session_state', config], + () => 'session_state' + ); + vi.spyOn(loggerService, 'logDebug').mockImplementation(() => undefined); (checkSessionService as any).pollServerSession('clientId', config); expect((checkSessionService as any).outstandingMessages).toBe(1); }); it('logs warning if iframe does not exist', () => { - vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( - null - ); + vi.spyOn( + checkSessionService, + 'getExistingIframe' + ).mockReturnValue(null); const authWellKnownEndpoints = { checkSessionIframe: 'https://some-testing-url.com', }; @@ -238,9 +251,10 @@ describe('CheckSessionService', () => { }); it('logs warning if clientId is not set', () => { - vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( - {} - ); + vi.spyOn( + checkSessionService, + 'getExistingIframe' + ).mockReturnValue({}); const authWellKnownEndpoints = { checkSessionIframe: 'https://some-testing-url.com', }; @@ -264,21 +278,24 @@ describe('CheckSessionService', () => { }); it('logs debug if session_state is not set', () => { - vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( - {} - ); + vi.spyOn( + checkSessionService, + 'getExistingIframe' + ).mockReturnValue({}); const authWellKnownEndpoints = { checkSessionIframe: 'https://some-testing-url.com', }; const config = { configId: 'configId1' }; mockImplementationWhenArgsEqual( - vi.spyOn(storagePersistenceService, 'read'), - ['authWellKnownEndPoints', config], - () => authWellKnownEndpoints - ) - .withArgs('session_state', config) - .mockReturnValue(null); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints + ), + ['session_state', config], + () => null + ); const spyLogDebug = vi .spyOn(loggerService, 'logDebug') @@ -289,19 +306,23 @@ describe('CheckSessionService', () => { }); it('logs debug if session_state is set but authWellKnownEndpoints are not set', () => { - vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( - {} + vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue( + {} as any ); + // biome-ignore lint/suspicious/noEvolvingTypes: const authWellKnownEndpoints = null; const config = { configId: 'configId1' }; mockImplementationWhenArgsEqual( - vi.spyOn(storagePersistenceService, 'read'), - ['authWellKnownEndPoints', config], - () => authWellKnownEndpoints - ) - .withArgs('session_state', config) - .mockReturnValue('some_session_state'); + mockImplementationWhenArgsEqual( + vi.spyOn(storagePersistenceService, 'read'), + ['authWellKnownEndPoints', config], + () => authWellKnownEndpoints + ), + ['session_state', config], + () => 'some_session_state' + ); + const spyLogDebug = vi .spyOn(loggerService, 'logDebug') .mockImplementation(() => undefined); @@ -321,7 +342,7 @@ describe('CheckSessionService', () => { serviceAsAny.iframeRefreshInterval = lastRefresh; const result = await lastValueFrom(serviceAsAny.init()); -expect(result).toBeUndefined(); + expect(result).toBeUndefined(); }); }); @@ -345,9 +366,10 @@ expect(result).toBeUndefined(); describe('checkSessionChanged$', () => { it('emits when internal event is thrown', async () => { - const result = await lastValueFrom(checkSessionService.checkSessionChanged$ - .pipe(skip(1))); -expect(result).toBe(true); + const result = await lastValueFrom( + checkSessionService.checkSessionChanged$.pipe(skip(1)) + ); + expect(result).toBe(true); const serviceAsAny = checkSessionService as any; @@ -355,17 +377,21 @@ expect(result).toBe(true); }); it('emits false initially', async () => { - const result = await lastValueFrom(checkSessionService.checkSessionChanged$); -expect(result).toBe(false); + const result = await lastValueFrom( + checkSessionService.checkSessionChanged$ + ); + expect(result).toBe(false); }); it('emits false then true when emitted', async () => { const expectedResultsInOrder = [false, true]; let counter = 0; - const result = await lastValueFrom(checkSessionService.checkSessionChanged$); -expect(result).toBe(expectedResultsInOrder[counter]);; -counter++; + const result = await lastValueFrom( + checkSessionService.checkSessionChanged$ + ); + expect(result).toBe(expectedResultsInOrder[counter]); + counter++; (checkSessionService as any).checkSessionChangedInternal$.next(true); }); diff --git a/src/iframe/existing-iframe.service.ts b/src/iframe/existing-iframe.service.ts index 2731213..5d3d2a9 100644 --- a/src/iframe/existing-iframe.service.ts +++ b/src/iframe/existing-iframe.service.ts @@ -1,6 +1,6 @@ +import { Injectable, inject } from 'injection-js'; import { DOCUMENT } from '../../dom'; -import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; @Injectable() @@ -52,7 +52,7 @@ export class IFrameService { } return null; - } catch (e) { + } catch { return null; } } diff --git a/src/iframe/silent-renew.service.spec.ts b/src/iframe/silent-renew.service.spec.ts index afb6981..9244f92 100644 --- a/src/iframe/silent-renew.service.spec.ts +++ b/src/iframe/silent-renew.service.spec.ts @@ -1,5 +1,5 @@ -import { TestBed, fakeAsync, tick } from '@/testing'; -import { Observable, of, throwError } from 'rxjs'; +import { TestBed } from '@/testing'; +import { Observable, lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ImplicitFlowCallbackService } from '../callback/implicit-flow-callback.service'; @@ -42,9 +42,6 @@ describe('SilentRenewService ', () => { FlowHelper, ], }); - }); - - beforeEach(() => { silentRenewService = TestBed.inject(SilentRenewService); iFrameService = TestBed.inject(IFrameService); flowHelper = TestBed.inject(FlowHelper); @@ -152,13 +149,18 @@ describe('SilentRenewService ', () => { const urlParts = 'code=some-code&state=some-state&session_state=some-session-state'; - await lastValueFrom(silentRenewService - .codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs)); -expect(spy).toHaveBeenCalledExactlyOnceWith( - expectedContext, - config, - allConfigs - ); + await lastValueFrom( + silentRenewService.codeFlowCallbackSilentRenewIframe( + [url, urlParts], + config, + allConfigs + ) + ); + expect(spy).toHaveBeenCalledExactlyOnceWith( + expectedContext, + config, + allConfigs + ); }); it('throws error if url has error param and resets everything on error', async () => { @@ -185,25 +187,29 @@ expect(spy).toHaveBeenCalledExactlyOnceWith( const url = 'url-part-1'; const urlParts = 'error=some_error'; - silentRenewService - .codeFlowCallbackSilentRenewIframe([url, urlParts], config, allConfigs) - .subscribe({ - error: (error) => { - expect(error).toEqual(new Error('some_error')); - expect(spy).not.toHaveBeenCalled(); - expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({ - isAuthenticated: false, - validationResult: ValidationResult.LoginRequired, - isRenewProcess: true, - }); - expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith( - config, - allConfigs - ); - expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config); - expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1); - }, + try { + await lastValueFrom( + silentRenewService.codeFlowCallbackSilentRenewIframe( + [url, urlParts], + config, + allConfigs + ) + ); + } catch (error) { + expect(error).toEqual(new Error('some_error')); + expect(spy).not.toHaveBeenCalled(); + expect(authStateServiceSpy).toHaveBeenCalledExactlyOnceWith({ + isAuthenticated: false, + validationResult: ValidationResult.LoginRequired, + isRenewProcess: true, }); + expect(resetAuthorizationDataSpy).toHaveBeenCalledExactlyOnceWith( + config, + allConfigs + ); + expect(setNonceSpy).toHaveBeenCalledExactlyOnceWith('', config); + expect(stopPeriodicTokenCheckSpy).toHaveBeenCalledTimes(1); + } }); }); @@ -306,10 +312,12 @@ expect(spy).toHaveBeenCalledExactlyOnceWith( const eventData = { detail: 'detail?detail2' } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; - const result = await lastValueFrom(silentRenewService.refreshSessionWithIFrameCompleted$); -expect(result).toEqual({ - refreshToken: 'callbackContext', - } as CallbackContext); + const result = await lastValueFrom( + silentRenewService.refreshSessionWithIFrameCompleted$ + ); + expect(result).toEqual({ + refreshToken: 'callbackContext', + } as CallbackContext); silentRenewService.silentRenewEventHandler( eventData, @@ -352,8 +360,10 @@ expect(result).toEqual({ const eventData = { detail: 'detail?detail2' } as CustomEvent; const allConfigs = [{ configId: 'configId1' }]; - const result = await lastValueFrom(silentRenewService.refreshSessionWithIFrameCompleted$); -expect(result).toBeNull(); + const result = await lastValueFrom( + silentRenewService.refreshSessionWithIFrameCompleted$ + ); + expect(result).toBeNull(); silentRenewService.silentRenewEventHandler( eventData, diff --git a/src/iframe/silent-renew.service.ts b/src/iframe/silent-renew.service.ts index 62cdef1..e189c30 100644 --- a/src/iframe/silent-renew.service.ts +++ b/src/iframe/silent-renew.service.ts @@ -153,7 +153,7 @@ export class SilentRenewService { this.flowsDataService.resetSilentRenewRunning(config); }, error: (err: unknown) => { - this.loggerService.logError(config, 'Error: ' + err); + this.loggerService.logError(config, `Error: ${err}`); this.refreshSessionWithIFrameCompletedInternal$.next(null); this.flowsDataService.resetSilentRenewRunning(config); }, diff --git a/src/interceptor/auth.interceptor.spec.ts b/src/interceptor/auth.interceptor.spec.ts index cb759a2..0c1ef04 100644 --- a/src/interceptor/auth.interceptor.spec.ts +++ b/src/interceptor/auth.interceptor.spec.ts @@ -10,6 +10,7 @@ import { HttpTestingController, provideHttpClientTesting, } from '@ngify/http/testing'; +import { lastValueFrom } from 'rxjs'; import { vi } from 'vitest'; import { AuthStateService } from '../auth-state/auth-state.service'; import { ConfigurationService } from '../config/config.service'; @@ -18,14 +19,14 @@ import { mockProvider } from '../testing/mock'; import { AuthInterceptor, authInterceptor } from './auth.interceptor'; import { ClosestMatchingRouteService } from './closest-matching-route.service'; -describe(`AuthHttpInterceptor`, () => { +describe('AuthHttpInterceptor', () => { let httpTestingController: HttpTestingController; let configurationService: ConfigurationService; let httpClient: HttpClient; let authStateService: AuthStateService; let closestMatchingRouteService: ClosestMatchingRouteService; - describe(`with Class Interceptor`, () => { + describe('with Class Interceptor', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [], @@ -51,6 +52,7 @@ describe(`AuthHttpInterceptor`, () => { closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { httpTestingController.verify(); }); @@ -58,7 +60,7 @@ describe(`AuthHttpInterceptor`, () => { runTests(); }); - describe(`with Functional Interceptor`, () => { + describe('with Functional Interceptor', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ @@ -78,6 +80,7 @@ describe(`AuthHttpInterceptor`, () => { closestMatchingRouteService = TestBed.inject(ClosestMatchingRouteService); }); + // biome-ignore lint/correctness/noUndeclaredVariables: afterEach(() => { httpTestingController.verify(); }); @@ -87,7 +90,7 @@ describe(`AuthHttpInterceptor`, () => { function runTests(): void { it('should add an Authorization header when route matches and token is present', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { @@ -104,7 +107,7 @@ describe(`AuthHttpInterceptor`, () => { ); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -115,7 +118,7 @@ expect(response).toBeTruthy(); }); it('should not add an Authorization header when `secureRoutes` is not given', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { @@ -130,7 +133,7 @@ expect(response).toBeTruthy(); ); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -141,7 +144,7 @@ expect(response).toBeTruthy(); }); it('should not add an Authorization header when no routes configured', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { @@ -158,7 +161,7 @@ expect(response).toBeTruthy(); ); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -169,7 +172,7 @@ expect(response).toBeTruthy(); }); it('should not add an Authorization header when no routes configured', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { @@ -183,7 +186,7 @@ expect(response).toBeTruthy(); ); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -194,7 +197,7 @@ expect(response).toBeTruthy(); }); it('should not add an Authorization header when route is configured but no token is present', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { @@ -209,7 +212,7 @@ expect(response).toBeTruthy(); vi.spyOn(authStateService, 'getAccessToken').mockReturnValue(''); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -220,14 +223,14 @@ expect(response).toBeTruthy(); }); it('should not add an Authorization header when no config is present', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( false ); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -241,7 +244,7 @@ expect(response).toBeTruthy(); vi.spyOn(configurationService, 'hasAtLeastOneConfig').mockReturnValue( true ); - const actionUrl = `https://jsonplaceholder.typicode.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { @@ -258,7 +261,7 @@ expect(response).toBeTruthy(); }); const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); @@ -269,8 +272,8 @@ expect(response).toBeTruthy(); }); it('should add an Authorization header when multiple routes are configured and token is present', async () => { - const actionUrl = `https://jsonplaceholder.typicode.com/`; - const actionUrl2 = `https://some-other-url.com/`; + const actionUrl = 'https://jsonplaceholder.typicode.com/'; + const actionUrl2 = 'https://some-other-url.com/'; vi.spyOn(configurationService, 'getAllConfigurations').mockReturnValue([ { secureRoutes: [actionUrl, actionUrl2], configId: 'configId1' }, @@ -283,11 +286,11 @@ expect(response).toBeTruthy(); true ); - const response = await lastValueFrom(httpClient.get(actionUrl)); -expect(response).toBeTruthy(); + let response = await lastValueFrom(httpClient.get(actionUrl)); + expect(response).toBeTruthy(); - const response = await lastValueFrom(httpClient.get(actionUrl2)); -expect(response).toBeTruthy(); + response = await lastValueFrom(httpClient.get(actionUrl2)); + expect(response).toBeTruthy(); const httpRequest = httpTestingController.expectOne(actionUrl); diff --git a/src/interceptor/auth.interceptor.ts b/src/interceptor/auth.interceptor.ts index 22ab04f..fee8365 100644 --- a/src/interceptor/auth.interceptor.ts +++ b/src/interceptor/auth.interceptor.ts @@ -73,7 +73,7 @@ function interceptRequest( if (allRoutesConfiguredFlat.length === 0) { deps.loggerService.logDebug( allConfigurations[0], - `No routes to check configured` + 'No routes to check configured' ); return next(req); @@ -114,7 +114,7 @@ function interceptRequest( `'${req.url}' matches configured route '${matchingRoute}', adding token` ); req = req.clone({ - headers: req.headers.set('Authorization', 'Bearer ' + token), + headers: req.headers.set('Authorization', `Bearer ${token}`), }); return next(req); diff --git a/src/interceptor/closest-matching-route.service.spec.ts b/src/interceptor/closest-matching-route.service.spec.ts index ecf8746..3b0b381 100644 --- a/src/interceptor/closest-matching-route.service.spec.ts +++ b/src/interceptor/closest-matching-route.service.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; import { LoggerService } from '../logging/logger.service'; import { mockProvider } from '../testing/mock'; import { ClosestMatchingRouteService } from './closest-matching-route.service'; @@ -11,9 +10,6 @@ describe('ClosestMatchingRouteService', () => { TestBed.configureTestingModule({ providers: [ClosestMatchingRouteService, mockProvider(LoggerService)], }); - }); - - beforeEach(() => { service = TestBed.inject(ClosestMatchingRouteService); }); diff --git a/src/interceptor/closest-matching-route.service.ts b/src/interceptor/closest-matching-route.service.ts index ddbe51c..a478bac 100644 --- a/src/interceptor/closest-matching-route.service.ts +++ b/src/interceptor/closest-matching-route.service.ts @@ -1,5 +1,5 @@ import { Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; @Injectable() export class ClosestMatchingRouteService { diff --git a/src/logging/console-logger.service.ts b/src/logging/console-logger.service.ts index 2aa9df6..4fd0733 100644 --- a/src/logging/console-logger.service.ts +++ b/src/logging/console-logger.service.ts @@ -1,17 +1,20 @@ import { Injectable } from 'injection-js'; -import { AbstractLoggerService } from './abstract-logger.service'; +import type { AbstractLoggerService } from './abstract-logger.service'; @Injectable() export class ConsoleLoggerService implements AbstractLoggerService { logError(message: string | object, ...args: any[]): void { + // biome-ignore lint/suspicious/noConsole: console.error(message, ...args); } logWarning(message: string | object, ...args: any[]): void { + // biome-ignore lint/suspicious/noConsole: console.warn(message, ...args); } logDebug(message: string | object, ...args: any[]): void { + // biome-ignore lint/suspicious/noConsole: console.debug(message, ...args); } } diff --git a/src/logging/log-level.ts b/src/logging/log-level.ts index 3e4e117..eb8faf3 100644 --- a/src/logging/log-level.ts +++ b/src/logging/log-level.ts @@ -1,6 +1,6 @@ export enum LogLevel { - None, - Debug, - Warn, - Error, + None = 0, + Debug = 1, + Warn = 2, + Error = 3, } diff --git a/src/logging/logger.service.spec.ts b/src/logging/logger.service.spec.ts index 30111c9..4057773 100644 --- a/src/logging/logger.service.spec.ts +++ b/src/logging/logger.service.spec.ts @@ -15,9 +15,6 @@ describe('Logger Service', () => { { provide: AbstractLoggerService, useClass: ConsoleLoggerService }, ], }); - }); - - beforeEach(() => { loggerService = TestBed.inject(LoggerService); }); diff --git a/src/login/login.service.spec.ts b/src/login/login.service.spec.ts index 87b098e..d9b1c48 100644 --- a/src/login/login.service.spec.ts +++ b/src/login/login.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@/testing'; import { CommonModule } from '@angular/common'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { mockProvider } from '../testing/mock'; @@ -31,9 +31,6 @@ describe('LoginService', () => { mockProvider(PopUpService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(LoginService); parLoginService = TestBed.inject(ParLoginService); popUpLoginService = TestBed.inject(PopUpLoginService); @@ -89,6 +86,7 @@ describe('LoginService', () => { it("should throw error if configuration is null and doesn't call loginPar or loginStandard", () => { // arrange + // biome-ignore lint/suspicious/noEvolvingTypes: const config = null; const loginParSpy = vi.spyOn(parLoginService, 'loginPar'); const standardLoginSpy = vi.spyOn(standardLoginService, 'loginStandard'); @@ -119,8 +117,8 @@ describe('LoginService', () => { // act await lastValueFrom(service.loginWithPopUp(config, [config])); -expect(loginWithPopUpPar).toHaveBeenCalledTimes(1);; -expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); + expect(loginWithPopUpPar).toHaveBeenCalledTimes(1); + expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); }); it('calls standardLoginService loginstandard if usePushedAuthorisationRequests is false', async () => { @@ -135,8 +133,8 @@ expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); // act await lastValueFrom(service.loginWithPopUp(config, [config])); -expect(loginWithPopUpPar).not.toHaveBeenCalled();; -expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1); + expect(loginWithPopUpPar).not.toHaveBeenCalled(); + expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1); }); it('stores the customParams to the storage if customParams are given', async () => { @@ -153,15 +151,17 @@ expect(loginWithPopUpStandardSpy).toHaveBeenCalledTimes(1); ); // act - await lastValueFrom(service.loginWithPopUp(config, [config], authOptions)); -expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( - 'storageCustomParamsAuthRequest', - { custom: 'params' }, - config - ); + await lastValueFrom( + service.loginWithPopUp(config, [config], authOptions) + ); + expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( + 'storageCustomParamsAuthRequest', + { custom: 'params' }, + config + ); }); - it('returns error if there is already a popup open', () => { + it('returns error if there is already a popup open', async () => { // arrange const config = { usePushedAuthorisationRequests: false }; const authOptions = { customParams: { custom: 'params' } }; @@ -175,13 +175,14 @@ expect(storagePersistenceServiceSpy).toHaveBeenCalledExactlyOnceWith( vi.spyOn(popUpService, 'isCurrentlyInPopup').mockReturnValue(true); // act - const result = await lastValueFrom(service - .loginWithPopUp(config, [config], authOptions)); -expect(result).toEqual({ - errorMessage: 'There is already a popup open.', - } as LoginResponse);; -expect(loginWithPopUpPar).not.toHaveBeenCalled();; -expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); + const result = await lastValueFrom( + service.loginWithPopUp(config, [config], authOptions) + ); + expect(result).toEqual({ + errorMessage: 'There is already a popup open.', + } as LoginResponse); + expect(loginWithPopUpPar).not.toHaveBeenCalled(); + expect(loginWithPopUpStandardSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/src/login/par/par-login.service.spec.ts b/src/login/par/par-login.service.spec.ts index c028f4e..6ffd68a 100644 --- a/src/login/par/par-login.service.spec.ts +++ b/src/login/par/par-login.service.spec.ts @@ -1,5 +1,5 @@ -import { TestBed } from '@/testing'; -import { of } from 'rxjs'; +import { TestBed, spyOnProperty } from '@/testing'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { CheckAuthService } from '../../auth-state/check-auth.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; @@ -40,9 +40,6 @@ describe('ParLoginService', () => { mockProvider(ParService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(ParLoginService); loggerService = TestBed.inject(LoggerService); responseTypeValidationService = TestBed.inject( @@ -68,7 +65,7 @@ describe('ParLoginService', () => { ).mockReturnValue(false); const loggerSpy = vi.spyOn(loggerService, 'logError'); - const result = service.loginPar({}); + const result = await lastValueFrom(service.loginPar({})); expect(result).toBeUndefined(); expect(loggerSpy).toHaveBeenCalled(); @@ -89,10 +86,12 @@ describe('ParLoginService', () => { .spyOn(parService, 'postParRequest') .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); - const result = service.loginPar({ - authWellknownEndpointUrl: 'authWellknownEndpoint', - responseType: 'stubValue', - }); + const result = await lastValueFrom( + service.loginPar({ + authWellknownEndpointUrl: 'authWellknownEndpoint', + responseType: 'stubValue', + }) + ); expect(result).toBeUndefined(); expect(spy).toHaveBeenCalled(); @@ -117,9 +116,11 @@ describe('ParLoginService', () => { .spyOn(parService, 'postParRequest') .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); - const result = service.loginPar(config, { - customParams: { some: 'thing' }, - }); + const result = await lastValueFrom( + service.loginPar(config, { + customParams: { some: 'thing' }, + }) + ); expect(result).toBeUndefined(); expect(spy).toHaveBeenCalledExactlyOnceWith(config, { @@ -148,7 +149,7 @@ describe('ParLoginService', () => { vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(''); const spy = vi.spyOn(loggerService, 'logError'); - const result = service.loginPar(config); + const result = await lastValueFrom(service.loginPar(config)); expect(result).toBeUndefined(); expect(spy).toHaveBeenCalledTimes(1); @@ -179,7 +180,7 @@ describe('ParLoginService', () => { ); const spy = vi.spyOn(redirectService, 'redirectTo'); - service.loginPar(config, authOptions); + await lastValueFrom(service.loginPar(config, authOptions)); expect(spy).toHaveBeenCalledExactlyOnceWith('some-par-url'); }); @@ -206,7 +207,7 @@ describe('ParLoginService', () => { 'some-par-url' ); const redirectToSpy = vi.spyOn(redirectService, 'redirectTo'); - const spy = jasmine.createSpy(); + const spy = vi.fn(); const urlHandler = (url: any): void => { spy(url); }; @@ -228,12 +229,12 @@ describe('ParLoginService', () => { const config = {}; const allConfigs = [config]; - service.loginWithPopUpPar(config, allConfigs).subscribe({ - error: (err) => { - expect(loggerSpy).toHaveBeenCalled(); - expect(err.message).toBe('Invalid response type!'); - }, - }); + try { + await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); + } catch (err: any) { + expect(loggerSpy).toHaveBeenCalled(); + expect(err.message).toBe('Invalid response type!'); + } }); it('calls parService.postParRequest without custom params when no custom params are passed', async () => { @@ -256,14 +257,14 @@ describe('ParLoginService', () => { .spyOn(parService, 'postParRequest') .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); - service.loginWithPopUpPar(config, allConfigs).subscribe({ - error: (err) => { - expect(spy).toHaveBeenCalled(); - expect(err.message).toBe( - "Could not create URL with param requestUri: 'url'" - ); - }, - }); + try { + await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); + } catch (err: any) { + expect(spy).toHaveBeenCalled(); + expect(err.message).toBe( + "Could not create URL with param requestUri: 'url'" + ); + } }); it('calls parService.postParRequest with custom params when custom params are passed', async () => { @@ -286,20 +287,20 @@ describe('ParLoginService', () => { .spyOn(parService, 'postParRequest') .mockReturnValue(of({ requestUri: 'requestUri' } as ParResponse)); - service - .loginWithPopUpPar(config, allConfigs, { + try { + await lastValueFrom( + service.loginWithPopUpPar(config, allConfigs, { + customParams: { some: 'thing' }, + }) + ); + } catch (err: any) { + expect(spy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { some: 'thing' }, - }) - .subscribe({ - error: (err) => { - expect(spy).toHaveBeenCalledExactlyOnceWith(config, { - customParams: { some: 'thing' }, - }); - expect(err.message).toBe( - "Could not create URL with param requestUri: 'url'" - ); - }, }); + expect(err.message).toBe( + "Could not create URL with param requestUri: 'url'" + ); + } }); it('returns undefined and logs error when no URL could be created', async () => { @@ -324,18 +325,18 @@ describe('ParLoginService', () => { vi.spyOn(urlService, 'getAuthorizeParUrl').mockReturnValue(''); const spy = vi.spyOn(loggerService, 'logError'); - service - .loginWithPopUpPar(config, allConfigs, { - customParams: { some: 'thing' }, - }) - .subscribe({ - error: (err) => { - expect(err.message).toBe( - "Could not create URL with param requestUri: 'url'" - ); - expect(spy).toHaveBeenCalledTimes(1); - }, - }); + try { + await lastValueFrom( + service.loginWithPopUpPar(config, allConfigs, { + customParams: { some: 'thing' }, + }) + ); + } catch (err: any) { + expect(err.message).toBe( + "Could not create URL with param requestUri: 'url'" + ); + expect(spy).toHaveBeenCalledTimes(1); + } }); it('calls popupService openPopUp when URL could be created', async () => { @@ -369,11 +370,11 @@ describe('ParLoginService', () => { const spy = vi.spyOn(popupService, 'openPopUp'); await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); -expect(spy).toHaveBeenCalledExactlyOnceWith( - 'some-par-url', - undefined, - config - ); + expect(spy).toHaveBeenCalledExactlyOnceWith( + 'some-par-url', + undefined, + config + ); }); it('returns correct properties if URL is received', async () => { @@ -418,19 +419,21 @@ expect(spy).toHaveBeenCalledExactlyOnceWith( spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); - const result = await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); -expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith( - config, - allConfigs, - 'someUrl' - );; -expect(result).toEqual({ - isAuthenticated: true, - configId: 'configId1', - idToken: '', - userData: { any: 'userData' }, - accessToken: 'anyAccessToken', - }); + const result = await lastValueFrom( + service.loginWithPopUpPar(config, allConfigs) + ); + expect(checkAuthSpy).toHaveBeenCalledExactlyOnceWith( + config, + allConfigs, + 'someUrl' + ); + expect(result).toEqual({ + isAuthenticated: true, + configId: 'configId1', + idToken: '', + userData: { any: 'userData' }, + accessToken: 'anyAccessToken', + }); }); it('returns correct properties if popup was closed by user', async () => { @@ -462,16 +465,18 @@ expect(result).toEqual({ spyOnProperty(popupService, 'result$').mockReturnValue(of(popupResult)); - const result = await lastValueFrom(service.loginWithPopUpPar(config, allConfigs)); -expect(checkAuthSpy).not.toHaveBeenCalled();; -expect(result).toEqual({ - isAuthenticated: false, - errorMessage: 'User closed popup', - configId: 'configId1', - idToken: '', - userData: null, - accessToken: '', - }); + const result = await lastValueFrom( + service.loginWithPopUpPar(config, allConfigs) + ); + expect(checkAuthSpy).not.toHaveBeenCalled(); + expect(result).toEqual({ + isAuthenticated: false, + errorMessage: 'User closed popup', + configId: 'configId1', + idToken: '', + userData: null, + accessToken: '', + }); }); }); }); diff --git a/src/login/par/par-login.service.ts b/src/login/par/par-login.service.ts index 7019d35..8bf81cf 100644 --- a/src/login/par/par-login.service.ts +++ b/src/login/par/par-login.service.ts @@ -1,6 +1,6 @@ import { Injectable, inject } from 'injection-js'; import { type Observable, of, throwError } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { map, shareReplay, switchMap, take } from 'rxjs/operators'; import type { AuthOptions } from '../../auth-options'; import { CheckAuthService } from '../../auth-state/check-auth.service'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; @@ -39,7 +39,7 @@ export class ParLoginService { loginPar( configuration: OpenIdConfiguration, authOptions?: AuthOptions - ): void { + ): Observable { if ( !this.responseTypeValidationService.hasConfigValidResponseType( configuration @@ -55,38 +55,53 @@ export class ParLoginService { 'BEGIN Authorize OIDC Flow, no auth data' ); - this.authWellKnownService + const result$ = this.authWellKnownService .queryAndStoreAuthWellKnownEndPoints(configuration) .pipe( switchMap(() => this.parService.postParRequest(configuration, authOptions) - ) - ) - .subscribe((response) => { - this.loggerService.logDebug(configuration, 'par response: ', response); + ), + map(() => { + (response) => { + this.loggerService.logDebug( + configuration, + 'par response: ', + response + ); - const url = this.urlService.getAuthorizeParUrl( - response.requestUri, - configuration - ); + const url = this.urlService.getAuthorizeParUrl( + response.requestUri, + configuration + ); - this.loggerService.logDebug(configuration, 'par request url: ', url); + this.loggerService.logDebug( + configuration, + 'par request url: ', + url + ); - if (!url) { - this.loggerService.logError( - configuration, - `Could not create URL with param ${response.requestUri}: '${url}'` - ); + if (!url) { + this.loggerService.logError( + configuration, + `Could not create URL with param ${response.requestUri}: '${url}'` + ); - return; - } + return; + } - if (authOptions?.urlHandler) { - authOptions.urlHandler(url); - } else { - this.redirectService.redirectTo(url); - } - }); + if (authOptions?.urlHandler) { + authOptions.urlHandler(url); + } else { + this.redirectService.redirectTo(url); + } + }; + }), + shareReplay(1) + ); + + result$.subscribe(); + + return result$; } loginWithPopUpPar( diff --git a/src/login/par/par.service.spec.ts b/src/login/par/par.service.spec.ts index eb61b95..d9799dc 100644 --- a/src/login/par/par.service.spec.ts +++ b/src/login/par/par.service.spec.ts @@ -1,6 +1,6 @@ -import { TestBed } from '@/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import { HttpHeaders } from '@ngify/http'; -import { of, throwError } from 'rxjs'; +import { lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { DataService } from '../../api/data.service'; import { LoggerService } from '../../logging/logger.service'; @@ -26,9 +26,6 @@ describe('ParService', () => { mockProvider(StoragePersistenceService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(ParService); dataService = TestBed.inject(DataService); loggerService = TestBed.inject(LoggerService); @@ -50,13 +47,13 @@ describe('ParService', () => { ['authWellKnownEndPoints', { configId: 'configId1' }], () => null ); - service.postParRequest({ configId: 'configId1' }).subscribe({ - error: (err) => { - expect(err.message).toBe( - 'Could not read PAR endpoint because authWellKnownEndPoints are not given' - ); - }, - }); + try { + await lastValueFrom(service.postParRequest({ configId: 'configId1' })); + } catch (err: any) { + expect(err.message).toBe( + 'Could not read PAR endpoint because authWellKnownEndPoints are not given' + ); + } }); it('throws error if par endpoint does not exist in storage', async () => { @@ -68,13 +65,13 @@ describe('ParService', () => { ['authWellKnownEndPoints', { configId: 'configId1' }], () => ({ some: 'thing' }) ); - service.postParRequest({ configId: 'configId1' }).subscribe({ - error: (err) => { - expect(err.message).toBe( - 'Could not read PAR endpoint from authWellKnownEndpoints' - ); - }, - }); + try { + await lastValueFrom(service.postParRequest({ configId: 'configId1' })); + } catch (err: any) { + expect(err.message).toBe( + 'Could not read PAR endpoint from authWellKnownEndpoints' + ); + } }); it('calls data service with correct params', async () => { @@ -92,12 +89,12 @@ describe('ParService', () => { .mockReturnValue(of({})); await lastValueFrom(service.postParRequest({ configId: 'configId1' })); -expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( - 'parEndpoint', - 'some-url123', - { configId: 'configId1' }, - expect.any(HttpHeaders) - ); + expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( + 'parEndpoint', + 'some-url123', + { configId: 'configId1' }, + expect.any(HttpHeaders) + ); }); it('Gives back correct object properties', async () => { @@ -112,8 +109,10 @@ expect(dataServiceSpy).toHaveBeenCalledExactlyOnceWith( vi.spyOn(dataService, 'post').mockReturnValue( of({ expires_in: 123, request_uri: 'request_uri' }) ); - const result = await lastValueFrom(service.postParRequest({ configId: 'configId1' })); -expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); + const result = await lastValueFrom( + service.postParRequest({ configId: 'configId1' }) + ); + expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); }); it('throws error if data service has got an error', async () => { @@ -130,18 +129,18 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); ); const loggerSpy = vi.spyOn(loggerService, 'logError'); - service.postParRequest({ configId: 'configId1' }).subscribe({ - error: (err) => { - expect(err.message).toBe( - 'There was an error on ParService postParRequest' - ); - expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( - { configId: 'configId1' }, - 'There was an error on ParService postParRequest', - expect.any(Error) - ); - }, - }); + try { + await lastValueFrom(service.postParRequest({ configId: 'configId1' })); + } catch (err: any) { + expect(err.message).toBe( + 'There was an error on ParService postParRequest' + ); + expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( + { configId: 'configId1' }, + 'There was an error on ParService postParRequest', + expect.any(Error) + ); + } }); it('should retry once', async () => { @@ -160,12 +159,11 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); ) ); - service.postParRequest({ configId: 'configId1' }).subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); - }, - }); + const res = await lastValueFrom( + service.postParRequest({ configId: 'configId1' }) + ); + expect(res).toBeTruthy(); + expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); }); it('should retry twice', async () => { @@ -185,12 +183,11 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); ) ); - service.postParRequest({ configId: 'configId1' }).subscribe({ - next: (res) => { - expect(res).toBeTruthy(); - expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); - }, - }); + const res = await lastValueFrom( + service.postParRequest({ configId: 'configId1' }) + ); + expect(res).toBeTruthy(); + expect(res).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); }); it('should fail after three tries', async () => { @@ -211,11 +208,11 @@ expect(result).toEqual({ expiresIn: 123, requestUri: 'request_uri' }); ) ); - service.postParRequest({ configId: 'configId1' }).subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(service.postParRequest({ configId: 'configId1' })); + } catch (err: any) { + expect(err).toBeTruthy(); + } }); }); }); diff --git a/src/login/par/par.service.ts b/src/login/par/par.service.ts index 9406487..606927c 100644 --- a/src/login/par/par.service.ts +++ b/src/login/par/par.service.ts @@ -1,14 +1,14 @@ import { HttpHeaders } from '@ngify/http'; import { inject, Injectable } from 'injection-js'; -import { Observable, throwError } from 'rxjs'; +import { type Observable, throwError } from 'rxjs'; import { catchError, map, retry, switchMap } from 'rxjs/operators'; import { DataService } from '../../api/data.service'; -import { AuthOptions } from '../../auth-options'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { AuthOptions } from '../../auth-options'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; import { UrlService } from '../../utils/url/url.service'; -import { ParResponse } from './par-response'; +import type { ParResponse } from './par-response'; @Injectable() export class ParService { @@ -74,7 +74,7 @@ export class ParService { }; }), catchError((error) => { - const errorMessage = `There was an error on ParService postParRequest`; + const errorMessage = 'There was an error on ParService postParRequest'; this.loggerService.logError(configuration, errorMessage, error); diff --git a/src/login/popup/popup-login.service.spec.ts b/src/login/popup/popup-login.service.spec.ts index 2ddf343..dbfbaba 100644 --- a/src/login/popup/popup-login.service.spec.ts +++ b/src/login/popup/popup-login.service.spec.ts @@ -59,12 +59,14 @@ describe('PopUpLoginService', () => { ).mockReturnValue(false); const loggerSpy = vi.spyOn(loggerService, 'logError'); - popUpLoginService.loginWithPopUpStandard(config, [config]).subscribe({ - error: (err) => { - expect(loggerSpy).toHaveBeenCalled(); - expect(err.message).toBe('Invalid response type!'); - }, - }); + try { + await lastValueFrom( + popUpLoginService.loginWithPopUpStandard(config, [config]) + ); + } catch (err: any) { + expect(loggerSpy).toHaveBeenCalled(); + expect(err.message).toBe('Invalid response type!'); + } }); it('calls urlService.getAuthorizeUrl() if everything fits', async () => { diff --git a/src/login/popup/popup.service.spec.ts b/src/login/popup/popup.service.spec.ts index 28d677d..3008425 100644 --- a/src/login/popup/popup.service.spec.ts +++ b/src/login/popup/popup.service.spec.ts @@ -1,5 +1,6 @@ -import { TestBed, fakeAsync, tick } from '@/testing'; -import { vi } from 'vitest'; +import { TestBed, spyOnProperty } from '@/testing'; +import { lastValueFrom } from 'rxjs'; +import { type MockInstance, vi } from 'vitest'; import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; @@ -113,7 +114,7 @@ describe('PopUpService', () => { }; const result = await lastValueFrom(popUpService.result$); -expect(result).toBe(popupResult); + expect(result).toBe(popupResult); (popUpService as any).resultInternal$.next(popupResult); }); @@ -122,7 +123,7 @@ expect(result).toBe(popupResult); describe('openPopup', () => { it('popup opens with parameters and default options', async () => { // arrange - const popupSpy = vi.spyOn(window, 'open').and.callFake( + const popupSpy = vi.spyOn(window, 'open').mockImplementation( () => ({ closed: true, @@ -143,7 +144,7 @@ expect(result).toBe(popupResult); it('popup opens with parameters and passed options', async () => { // arrange - const popupSpy = vi.spyOn(window, 'open').and.callFake( + const popupSpy = vi.spyOn(window, 'open').mockImplementation( () => ({ closed: true, @@ -180,9 +181,9 @@ expect(result).toBe(popupResult); describe('popup closed', () => { let popup: Window; let popupResult: PopupResult; - let cleanUpSpy: jasmine.Spy; + let cleanUpSpy: MockInstance; - beforeEach(() => { + beforeEach(async () => { popup = { closed: false, close: () => undefined, @@ -190,12 +191,12 @@ expect(result).toBe(popupResult); vi.spyOn(window, 'open').mockReturnValue(popup); - cleanUpSpy = vi.spyOn(popUpService as any, 'cleanUp')(); + cleanUpSpy = vi.spyOn(popUpService as any, 'cleanUp'); popupResult = {} as PopupResult; const result = await lastValueFrom(popUpService.result$); -(popupResult = result) + popupResult = result; }); it('message received with data', async () => { @@ -203,8 +204,10 @@ expect(result).toBe(popupResult); return; }; - vi.spyOn(window, 'addEventListener').and.callFake( - (_: any, func: any) => (listener = func) + vi.spyOn(window, 'addEventListener').mockImplementation( + // biome-ignore lint/suspicious/noAssignInExpressions: + // biome-ignore lint/complexity/noVoid: + (_: any, func: any) => void (listener = func) ); popUpService.openPopUp('url', {}, { configId: 'configId1' }); @@ -214,7 +217,7 @@ expect(result).toBe(popupResult); listener(new MessageEvent('message', { data: 'some-url1111' })); - tick(200); + await vi.advanceTimersByTimeAsync(200); expect(popupResult).toEqual({ userClosed: false, @@ -230,7 +233,8 @@ expect(result).toBe(popupResult); return; }; - vi.spyOn(window, 'addEventListener').and.callFake( + vi.spyOn(window, 'addEventListener').mockImplementation( + // biome-ignore lint/suspicious/noAssignInExpressions: (_: any, func: any) => (listener = func) ); const nextSpy = vi.spyOn((popUpService as any).resultInternal$, 'next'); @@ -242,7 +246,7 @@ expect(result).toBe(popupResult); listener(new MessageEvent('message', { data: null })); - tick(200); + vi.advanceTimersByTimeAsync(200); expect(popupResult).toEqual({} as PopupResult); expect(cleanUpSpy).toHaveBeenCalled(); @@ -257,7 +261,7 @@ expect(result).toBe(popupResult); (popup as any).closed = true; - tick(200); + await vi.advanceTimersByTimeAsync(200); expect(popupResult).toEqual({ userClosed: true, @@ -278,6 +282,8 @@ expect(result).toBe(popupResult); // act popUpService.sendMessageToMainWindow('', {}); + await vi.advanceTimersToNextFrame(); + // assert expect(sendMessageSpy).not.toHaveBeenCalled(); }); diff --git a/src/login/popup/popup.service.ts b/src/login/popup/popup.service.ts index 3bf6f65..b95bd8d 100644 --- a/src/login/popup/popup.service.ts +++ b/src/login/popup/popup.service.ts @@ -1,11 +1,11 @@ import { DOCUMENT } from '../../dom'; import { inject, Injectable } from 'injection-js'; -import { Observable, Subject } from 'rxjs'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import { type Observable, Subject } from 'rxjs'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; -import { PopupOptions } from './popup-options'; -import { PopupResult } from './popup-result'; +import type { PopupOptions } from './popup-options'; +import type { PopupResult } from './popup-result'; @Injectable() export class PopUpService { @@ -90,7 +90,7 @@ export class PopUpService { return; } - this.loggerService.logDebug(config, 'Opened popup with url ' + url); + this.loggerService.logDebug(config, `Opened popup with url ${url}`); const listener = (event: MessageEvent): void => { if (!event?.data || typeof event.data !== 'string') { @@ -104,7 +104,7 @@ export class PopUpService { this.loggerService.logDebug( config, - 'Received message from popup with url ' + event.data + `Received message from popup with url ${event.data}` ); this.resultInternal$.next({ userClosed: false, receivedUrl: event.data }); diff --git a/src/login/response-type-validation/response-type-validation.service.spec.ts b/src/login/response-type-validation/response-type-validation.service.spec.ts index d3e0583..a6c6961 100644 --- a/src/login/response-type-validation/response-type-validation.service.spec.ts +++ b/src/login/response-type-validation/response-type-validation.service.spec.ts @@ -18,9 +18,6 @@ describe('ResponseTypeValidationService', () => { mockProvider(FlowHelper), ], }); - }); - - beforeEach(() => { responseTypeValidationService = TestBed.inject( ResponseTypeValidationService ); diff --git a/src/login/response-type-validation/response-type-validation.service.ts b/src/login/response-type-validation/response-type-validation.service.ts index 0437ad2..d746284 100644 --- a/src/login/response-type-validation/response-type-validation.service.ts +++ b/src/login/response-type-validation/response-type-validation.service.ts @@ -1,5 +1,5 @@ import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; import { FlowHelper } from '../../utils/flowHelper/flow-helper.service'; diff --git a/src/login/standard/standard-login.service.spec.ts b/src/login/standard/standard-login.service.spec.ts index f655eaf..69f1e5a 100644 --- a/src/login/standard/standard-login.service.spec.ts +++ b/src/login/standard/standard-login.service.spec.ts @@ -1,5 +1,5 @@ -import { TestBed, fakeAsync, tick } from '@/testing'; -import { of } from 'rxjs'; +import { TestBed } from '@/testing'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; import { FlowsDataService } from '../../flows/flows-data.service'; @@ -32,9 +32,6 @@ describe('StandardLoginService', () => { mockProvider(FlowsDataService), ], }); - }); - - beforeEach(() => { standardLoginService = TestBed.inject(StandardLoginService); loggerService = TestBed.inject(LoggerService); responseTypeValidationService = TestBed.inject( @@ -59,9 +56,11 @@ describe('StandardLoginService', () => { ).mockReturnValue(false); const loggerSpy = vi.spyOn(loggerService, 'logError'); - const result = standardLoginService.loginStandard({ - configId: 'configId1', - }); + const result = await lastValueFrom( + standardLoginService.loginStandard({ + configId: 'configId1', + }) + ); expect(result).toBeUndefined(); expect(loggerSpy).toHaveBeenCalled(); @@ -84,7 +83,9 @@ describe('StandardLoginService', () => { vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); const flowsDataSpy = vi.spyOn(flowsDataService, 'setCodeFlowInProgress'); - const result = standardLoginService.loginStandard(config); + const result = await lastValueFrom( + standardLoginService.loginStandard(config) + ); expect(result).toBeUndefined(); expect(flowsDataSpy).toHaveBeenCalled(); @@ -106,7 +107,9 @@ describe('StandardLoginService', () => { ).mockReturnValue(of({})); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); - const result = standardLoginService.loginStandard(config); + const result = await lastValueFrom( + standardLoginService.loginStandard(config) + ); expect(result).toBeUndefined(); }); @@ -126,10 +129,10 @@ describe('StandardLoginService', () => { 'queryAndStoreAuthWellKnownEndPoints' ).mockReturnValue(of({})); vi.spyOn(urlService, 'getAuthorizeUrl').mockReturnValue(of('someUrl')); - const redirectSpy = vi.spyOn(redirectService, 'redirectTo')(); + const redirectSpy = vi.spyOn(redirectService, 'redirectTo'); standardLoginService.loginStandard(config); - tick(); + await vi.advanceTimersByTimeAsync(0); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl'); }); @@ -151,13 +154,13 @@ describe('StandardLoginService', () => { const redirectSpy = vi .spyOn(redirectService, 'redirectTo') .mockImplementation(() => undefined); - const spy = jasmine.createSpy(); + const spy = vi.fn(); const urlHandler = (url: any): void => { spy(url); }; standardLoginService.loginStandard(config, { urlHandler }); - tick(); + await vi.advanceTimersByTimeAsync(0); expect(spy).toHaveBeenCalledExactlyOnceWith('someUrl'); expect(redirectSpy).not.toHaveBeenCalled(); }); @@ -183,7 +186,7 @@ describe('StandardLoginService', () => { ); standardLoginService.loginStandard(config, {}); - tick(); + await vi.advanceTimersByTimeAsync(0); expect(flowsDataSpy).toHaveBeenCalled(); }); @@ -212,7 +215,7 @@ describe('StandardLoginService', () => { standardLoginService.loginStandard(config, { customParams: { to: 'add', as: 'well' }, }); - tick(); + await vi.advanceTimersByTimeAsync(0); expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someUrl'); expect(getAuthorizeUrlSpy).toHaveBeenCalledExactlyOnceWith(config, { customParams: { to: 'add', as: 'well' }, @@ -241,7 +244,7 @@ describe('StandardLoginService', () => { .mockImplementation(() => undefined); standardLoginService.loginStandard(config); - tick(); + await vi.advanceTimersByTimeAsync(0); expect(loggerSpy).toHaveBeenCalledExactlyOnceWith( config, 'Could not create URL', diff --git a/src/login/standard/standard-login.service.ts b/src/login/standard/standard-login.service.ts index d4e230d..6c32268 100644 --- a/src/login/standard/standard-login.service.ts +++ b/src/login/standard/standard-login.service.ts @@ -1,7 +1,8 @@ -import { inject, Injectable } from 'injection-js'; -import { AuthOptions } from '../../auth-options'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, map, shareReplay, switchMap } from 'rxjs'; +import type { AuthOptions } from '../../auth-options'; import { AuthWellKnownService } from '../../config/auth-well-known/auth-well-known.service'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { FlowsDataService } from '../../flows/flows-data.service'; import { LoggerService } from '../../logging/logger.service'; import { RedirectService } from '../../utils/redirect/redirect.service'; @@ -27,7 +28,7 @@ export class StandardLoginService { loginStandard( configuration: OpenIdConfiguration, authOptions?: AuthOptions - ): void { + ): Observable { if ( !this.responseTypeValidationService.hasConfigValidResponseType( configuration @@ -44,32 +45,37 @@ export class StandardLoginService { ); this.flowsDataService.setCodeFlowInProgress(configuration); - this.authWellKnownService + const result$ = this.authWellKnownService .queryAndStoreAuthWellKnownEndPoints(configuration) - .subscribe(() => { - const { urlHandler } = authOptions || {}; + .pipe( + switchMap(() => { + this.flowsDataService.resetSilentRenewRunning(configuration); - this.flowsDataService.resetSilentRenewRunning(configuration); + return this.urlService.getAuthorizeUrl(configuration, authOptions); + }), + map((url) => { + const { urlHandler } = authOptions || {}; + if (!url) { + this.loggerService.logError( + configuration, + 'Could not create URL', + url + ); - this.urlService - .getAuthorizeUrl(configuration, authOptions) - .subscribe((url) => { - if (!url) { - this.loggerService.logError( - configuration, - 'Could not create URL', - url - ); + return; + } - return; - } + if (urlHandler) { + urlHandler(url); + } else { + this.redirectService.redirectTo(url); + } + }), + shareReplay(1) + ); - if (urlHandler) { - urlHandler(url); - } else { - this.redirectService.redirectTo(url); - } - }); - }); + result$.subscribe(); + + return result$; } } diff --git a/src/logoff-revoke/logoff-revocation.service.spec.ts b/src/logoff-revoke/logoff-revocation.service.spec.ts index 5341f8f..7854d86 100644 --- a/src/logoff-revoke/logoff-revocation.service.spec.ts +++ b/src/logoff-revoke/logoff-revocation.service.spec.ts @@ -1,6 +1,6 @@ -import { TestBed } from '@/testing'; +import { TestBed, mockImplementationWhenArgsEqual } from '@/testing'; import type { HttpHeaders } from '@ngify/http'; -import { Observable, of, throwError } from 'rxjs'; +import { Observable, lastValueFrom, of, throwError } from 'rxjs'; import { vi } from 'vitest'; import { DataService } from '../api/data.service'; import { ResetAuthDataService } from '../flows/reset-auth-data.service'; @@ -35,9 +35,6 @@ describe('Logout and Revoke Service', () => { mockProvider(RedirectService), ], }); - }); - - beforeEach(() => { service = TestBed.inject(LogoffRevocationService); dataService = TestBed.inject(DataService); loggerService = TestBed.inject(LoggerService); @@ -124,8 +121,8 @@ describe('Logout and Revoke Service', () => { // Act const result = await lastValueFrom(service.revokeAccessToken(config)); -expect(result).toEqual({ data: 'anything' });; -expect(loggerSpy).toHaveBeenCalled(); + expect(result).toEqual({ data: 'anything' }); + expect(loggerSpy).toHaveBeenCalled(); }); it('loggs error when request is negative', async () => { @@ -144,12 +141,12 @@ expect(loggerSpy).toHaveBeenCalled(); ); // Act - service.revokeAccessToken(config).subscribe({ - error: (err) => { - expect(loggerSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(service.revokeAccessToken(config)); + } catch (err: any) { + expect(loggerSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); it('should retry once', async () => { @@ -170,14 +167,10 @@ expect(loggerSpy).toHaveBeenCalled(); ) ); - service.revokeAccessToken(config).subscribe({ - next: (res) => { - // Assert - expect(res).toBeTruthy(); - expect(res).toEqual({ data: 'anything' }); - expect(loggerSpy).toHaveBeenCalled(); - }, - }); + const res = await lastValueFrom(service.revokeAccessToken(config)); + expect(res).toBeTruthy(); + expect(res).toEqual({ data: 'anything' }); + expect(loggerSpy).toHaveBeenCalled(); }); it('should retry twice', async () => { @@ -199,14 +192,10 @@ expect(loggerSpy).toHaveBeenCalled(); ) ); - service.revokeAccessToken(config).subscribe({ - next: (res) => { - // Assert - expect(res).toBeTruthy(); - expect(res).toEqual({ data: 'anything' }); - expect(loggerSpy).toHaveBeenCalled(); - }, - }); + const res = await lastValueFrom(service.revokeAccessToken(config)); + expect(res).toBeTruthy(); + expect(res).toEqual({ data: 'anything' }); + expect(loggerSpy).toHaveBeenCalled(); }); it('should fail after three tries', async () => { @@ -229,12 +218,12 @@ expect(loggerSpy).toHaveBeenCalled(); ) ); - service.revokeAccessToken(config).subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - expect(loggerSpy).toHaveBeenCalled(); - }, - }); + try { + await lastValueFrom(service.revokeAccessToken(config)); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(loggerSpy).toHaveBeenCalled(); + } }); }); @@ -309,8 +298,8 @@ expect(loggerSpy).toHaveBeenCalled(); // Act const result = await lastValueFrom(service.revokeRefreshToken(config)); -expect(result).toEqual({ data: 'anything' });; -expect(loggerSpy).toHaveBeenCalled(); + expect(result).toEqual({ data: 'anything' }); + expect(loggerSpy).toHaveBeenCalled(); }); it('loggs error when request is negative', async () => { @@ -329,12 +318,12 @@ expect(loggerSpy).toHaveBeenCalled(); ); // Act - service.revokeRefreshToken(config).subscribe({ - error: (err) => { - expect(loggerSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(service.revokeRefreshToken(config)); + } catch (err: any) { + expect(loggerSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); it('should retry once', async () => { @@ -355,14 +344,10 @@ expect(loggerSpy).toHaveBeenCalled(); ) ); - service.revokeRefreshToken(config).subscribe({ - next: (res) => { - // Assert - expect(res).toBeTruthy(); - expect(res).toEqual({ data: 'anything' }); - expect(loggerSpy).toHaveBeenCalled(); - }, - }); + const res = await lastValueFrom(service.revokeRefreshToken(config)); + expect(res).toBeTruthy(); + expect(res).toEqual({ data: 'anything' }); + expect(loggerSpy).toHaveBeenCalled(); }); it('should retry twice', async () => { @@ -384,14 +369,10 @@ expect(loggerSpy).toHaveBeenCalled(); ) ); - service.revokeRefreshToken(config).subscribe({ - next: (res) => { - // Assert - expect(res).toBeTruthy(); - expect(res).toEqual({ data: 'anything' }); - expect(loggerSpy).toHaveBeenCalled(); - }, - }); + const res = await lastValueFrom(service.revokeRefreshToken(config)); + expect(res).toBeTruthy(); + expect(res).toEqual({ data: 'anything' }); + expect(loggerSpy).toHaveBeenCalled(); }); it('should fail after three tries', async () => { @@ -414,12 +395,12 @@ expect(loggerSpy).toHaveBeenCalled(); ) ); - service.revokeRefreshToken(config).subscribe({ - error: (err) => { - expect(err).toBeTruthy(); - expect(loggerSpy).toHaveBeenCalled(); - }, - }); + try { + await lastValueFrom(service.revokeRefreshToken(config)); + } catch (err: any) { + expect(err).toBeTruthy(); + expect(loggerSpy).toHaveBeenCalled(); + } }); }); @@ -439,7 +420,7 @@ expect(loggerSpy).toHaveBeenCalled(); // Assert await lastValueFrom(result$); -expect(serverStateChangedSpy).not.toHaveBeenCalled(); + expect(serverStateChangedSpy).not.toHaveBeenCalled(); }); it('logs and returns if `serverStateChanged` is true', async () => { @@ -455,13 +436,13 @@ expect(serverStateChangedSpy).not.toHaveBeenCalled(); // Assert await lastValueFrom(result$); -expect(redirectSpy).not.toHaveBeenCalled(); + expect(redirectSpy).not.toHaveBeenCalled(); }); it('calls urlHandler if urlhandler is passed', async () => { // Arrange vi.spyOn(urlService, 'getEndSessionUrl').mockReturnValue('someValue'); - const spy = jasmine.createSpy(); + const spy = vi.fn(); const urlHandler = (url: string): void => { spy(url); }; @@ -481,9 +462,9 @@ expect(redirectSpy).not.toHaveBeenCalled(); // Assert await lastValueFrom(result$); -expect(redirectSpy).not.toHaveBeenCalled();; -expect(spy).toHaveBeenCalledExactlyOnceWith('someValue');; -expect(resetAuthorizationDataSpy).toHaveBeenCalled(); + expect(redirectSpy).not.toHaveBeenCalled(); + expect(spy).toHaveBeenCalledExactlyOnceWith('someValue'); + expect(resetAuthorizationDataSpy).toHaveBeenCalled(); }); it('calls redirect service if no logoutOptions are passed', async () => { @@ -502,7 +483,7 @@ expect(resetAuthorizationDataSpy).toHaveBeenCalled(); // Assert await lastValueFrom(result$); -expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); + expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); }); it('calls redirect service if logoutOptions are passed and method is GET', async () => { @@ -521,7 +502,7 @@ expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); // Assert await lastValueFrom(result$); -expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); + expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); }); it('calls dataservice post if logoutOptions are passed and method is POST', async () => { @@ -553,22 +534,22 @@ expect(redirectSpy).toHaveBeenCalledExactlyOnceWith('someValue'); // Assert await lastValueFrom(result$); -expect(redirectSpy).not.toHaveBeenCalled();; -expect(postSpy).toHaveBeenCalledExactlyOnceWith( - 'some-url', - { - id_token_hint: 'id-token', - client_id: 'clientId', - post_logout_redirect_uri: 'post-logout-redirect-url', - }, - config, - expect.anything() - );; -const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; -expect(httpHeaders.has('Content-Type')).toBeTruthy();; -expect(httpHeaders.get('Content-Type')).toBe( - 'application/x-www-form-urlencoded' - ); + expect(redirectSpy).not.toHaveBeenCalled(); + expect(postSpy).toHaveBeenCalledExactlyOnceWith( + 'some-url', + { + id_token_hint: 'id-token', + client_id: 'clientId', + post_logout_redirect_uri: 'post-logout-redirect-url', + }, + config, + expect.anything() + ); + const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTruthy(); + 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', async () => { @@ -605,25 +586,25 @@ expect(httpHeaders.get('Content-Type')).toBe( // Assert await lastValueFrom(result$); -expect(redirectSpy).not.toHaveBeenCalled();; -expect(postSpy).toHaveBeenCalledExactlyOnceWith( - '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, - expect.anything() - );; -const httpHeaders = postSpy.calls.mostRecent().args[3] as HttpHeaders;; -expect(httpHeaders.has('Content-Type')).toBeTruthy();; -expect(httpHeaders.get('Content-Type')).toBe( - 'application/x-www-form-urlencoded' - ); + expect(redirectSpy).not.toHaveBeenCalled(); + expect(postSpy).toHaveBeenCalledExactlyOnceWith( + '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, + expect.anything() + ); + const httpHeaders = postSpy.mock.calls.at(-1)?.[3] as HttpHeaders; + expect(httpHeaders.has('Content-Type')).toBeTruthy(); + expect(httpHeaders.get('Content-Type')).toBe( + 'application/x-www-form-urlencoded' + ); }); }); @@ -667,8 +648,8 @@ expect(httpHeaders.get('Content-Type')).toBe( // Act await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); -expect(revokeRefreshTokenSpy).toHaveBeenCalled();; -expect(revokeAccessTokenSpy).toHaveBeenCalled(); + expect(revokeRefreshTokenSpy).toHaveBeenCalled(); + expect(revokeAccessTokenSpy).toHaveBeenCalled(); }); it('logs error when revokeaccesstoken throws an error', async () => { @@ -694,12 +675,12 @@ expect(revokeAccessTokenSpy).toHaveBeenCalled(); ); // Act - service.logoffAndRevokeTokens(config, [config]).subscribe({ - error: (err) => { - expect(loggerSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); + } catch (err: any) { + expect(loggerSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); it('calls logoff in case of success', async () => { @@ -720,7 +701,7 @@ expect(revokeAccessTokenSpy).toHaveBeenCalled(); // Act await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); -expect(logoffSpy).toHaveBeenCalled(); + expect(logoffSpy).toHaveBeenCalled(); }); it('calls logoff with urlhandler in case of success', async () => { @@ -741,11 +722,12 @@ expect(logoffSpy).toHaveBeenCalled(); const config = { configId: 'configId1' }; // Act - await lastValueFrom(service - .logoffAndRevokeTokens(config, [config], { urlHandler })); -expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], { - urlHandler, - }); + await lastValueFrom( + service.logoffAndRevokeTokens(config, [config], { urlHandler }) + ); + expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], { + urlHandler, + }); }); it('calls revokeAccessToken when storage does not hold a refreshtoken', async () => { @@ -768,8 +750,8 @@ expect(logoffSpy).toHaveBeenCalledExactlyOnceWith(config, [config], { // Act await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); -expect(revokeRefreshTokenSpy).not.toHaveBeenCalled();; -expect(revokeAccessTokenSpy).toHaveBeenCalled(); + expect(revokeRefreshTokenSpy).not.toHaveBeenCalled(); + expect(revokeAccessTokenSpy).toHaveBeenCalled(); }); it('logs error when revokeaccesstoken throws an error', async () => { @@ -791,12 +773,12 @@ expect(revokeAccessTokenSpy).toHaveBeenCalled(); ); // Act - service.logoffAndRevokeTokens(config, [config]).subscribe({ - error: (err) => { - expect(loggerSpy).toHaveBeenCalled(); - expect(err).toBeTruthy(); - }, - }); + try { + await lastValueFrom(service.logoffAndRevokeTokens(config, [config])); + } catch (err: any) { + expect(loggerSpy).toHaveBeenCalled(); + expect(err).toBeTruthy(); + } }); }); diff --git a/src/logoff-revoke/logoff-revocation.service.ts b/src/logoff-revoke/logoff-revocation.service.ts index 8988c59..7e1f20b 100644 --- a/src/logoff-revoke/logoff-revocation.service.ts +++ b/src/logoff-revoke/logoff-revocation.service.ts @@ -141,18 +141,7 @@ export class LogoffRevocationService { return this.revokeRefreshToken(config).pipe( switchMap((_) => this.revokeAccessToken(config)), catchError((error) => { - const errorMessage = `revoke token failed`; - - this.loggerService.logError(config, errorMessage, error); - - return throwError(() => new Error(errorMessage)); - }), - concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions)) - ); - } else { - return this.revokeAccessToken(config).pipe( - catchError((error) => { - const errorMessage = `revoke accessToken failed`; + const errorMessage = 'revoke token failed'; this.loggerService.logError(config, errorMessage, error); @@ -161,6 +150,16 @@ export class LogoffRevocationService { concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions)) ); } + return this.revokeAccessToken(config).pipe( + catchError((error) => { + const errorMessage = 'revoke accessToken failed'; + + this.loggerService.logError(config, errorMessage, error); + + return throwError(() => new Error(errorMessage)); + }), + concatMap(() => this.logoff(config, allConfigs, logoutAuthOptions)) + ); } // https://tools.ietf.org/html/rfc7009 @@ -281,7 +280,7 @@ export class LogoffRevocationService { return of(response); }), catchError((error) => { - const errorMessage = `Revocation request failed`; + const errorMessage = 'Revocation request failed'; this.loggerService.logError(configuration, errorMessage, error); diff --git a/src/public-events/event-types.ts b/src/public-events/event-types.ts index 41b5bba..daa05fd 100644 --- a/src/public-events/event-types.ts +++ b/src/public-events/event-types.ts @@ -1,4 +1,3 @@ -// biome-ignore lint/nursery/noEnum: export enum EventTypes { /** * This only works in the AppModule Constructor diff --git a/src/public-events/notification.ts b/src/public-events/notification.ts index ec9d0ec..a789de4 100644 --- a/src/public-events/notification.ts +++ b/src/public-events/notification.ts @@ -1,4 +1,4 @@ -import { EventTypes } from './event-types'; +import type { EventTypes } from './event-types'; export interface OidcClientNotification { type: EventTypes; diff --git a/src/public-events/public-events.service.spec.ts b/src/public-events/public-events.service.spec.ts index 1e16310..eedbe0f 100644 --- a/src/public-events/public-events.service.spec.ts +++ b/src/public-events/public-events.service.spec.ts @@ -1,4 +1,5 @@ import { TestBed } from '@/testing'; +import { lastValueFrom } from 'rxjs'; import { filter } from 'rxjs/operators'; import { vi } from 'vitest'; import { EventTypes } from './event-types'; @@ -11,9 +12,6 @@ describe('Events Service', () => { TestBed.configureTestingModule({ providers: [PublicEventsService], }); - }); - - beforeEach(() => { eventsService = TestBed.inject(PublicEventsService); }); @@ -23,20 +21,20 @@ describe('Events Service', () => { it('registering to single event with one event emit works', async () => { const firedEvent = await lastValueFrom(eventsService.registerForEvents()); -expect(firedEvent).toBeTruthy();; -expect(firedEvent).toEqual({ - type: EventTypes.ConfigLoaded, - value: { myKey: 'myValue' }, - }); + expect(firedEvent).toBeTruthy(); + expect(firedEvent).toEqual({ + type: EventTypes.ConfigLoaded, + value: { myKey: 'myValue' }, + }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); }); it('registering to single event with multiple same event emit works', async () => { - const spy = jasmine.createSpy('spy'); + const spy = vi.fn()('spy'); const firedEvent = await lastValueFrom(eventsService.registerForEvents()); -spy(firedEvent);; -expect(firedEvent).toBeTruthy(); + spy(firedEvent); + expect(firedEvent).toBeTruthy(); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue2' }); @@ -45,21 +43,23 @@ expect(firedEvent).toBeTruthy(); type: EventTypes.ConfigLoaded, value: { myKey: 'myValue' }, }); - expect(spy.calls.mostRecent().args[0]).toEqual({ + expect(spy.postSpy.mock.calls.at(-1)?.[0]).toEqual({ type: EventTypes.ConfigLoaded, value: { myKey: 'myValue2' }, }); }); it('registering to single event with multiple emit works', async () => { - const firedEvent = await lastValueFrom(eventsService - .registerForEvents() - .pipe(filter((x) => x.type === EventTypes.ConfigLoaded))); -expect(firedEvent).toBeTruthy();; -expect(firedEvent).toEqual({ - type: EventTypes.ConfigLoaded, - value: { myKey: 'myValue' }, - }); + const firedEvent = await lastValueFrom( + eventsService + .registerForEvents() + .pipe(filter((x) => x.type === EventTypes.ConfigLoaded)) + ); + expect(firedEvent).toBeTruthy(); + expect(firedEvent).toEqual({ + type: EventTypes.ConfigLoaded, + value: { myKey: 'myValue' }, + }); eventsService.fireEvent(EventTypes.ConfigLoaded, { myKey: 'myValue' }); eventsService.fireEvent(EventTypes.NewAuthenticationResult, true); }); diff --git a/src/public-events/public-events.service.ts b/src/public-events/public-events.service.ts index 030edec..3a96091 100644 --- a/src/public-events/public-events.service.ts +++ b/src/public-events/public-events.service.ts @@ -1,7 +1,7 @@ import { Injectable } from 'injection-js'; -import { Observable, ReplaySubject } from 'rxjs'; -import { EventTypes } from './event-types'; -import { OidcClientNotification } from './notification'; +import { type Observable, ReplaySubject } from 'rxjs'; +import type { EventTypes } from './event-types'; +import type { OidcClientNotification } from './notification'; @Injectable() export class PublicEventsService { diff --git a/src/router/index.ts b/src/router/index.ts index c0f1f96..1a6825d 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -11,7 +11,7 @@ export interface RouterStateSnapshot { } export abstract class AbstractRouter { - navigateByUrl(url: string): void { + navigateByUrl(_url: string): void { // TODO // Implementation of navigating to a URL } @@ -23,5 +23,9 @@ export abstract class AbstractRouter { } // TODO - parseUrl(url: string) {} + parseUrl(_url: string): any { + // TODO + // Implementation of getting the current navigation + return null; + } } diff --git a/src/storage/browser-storage.service.spec.ts b/src/storage/browser-storage.service.spec.ts index 9fa3f97..9df218a 100644 --- a/src/storage/browser-storage.service.spec.ts +++ b/src/storage/browser-storage.service.spec.ts @@ -20,9 +20,6 @@ describe('BrowserStorageService', () => { }, ], }); - }); - - beforeEach(() => { abstractSecurityStorage = TestBed.inject(AbstractSecurityStorage); service = TestBed.inject(BrowserStorageService); }); @@ -76,7 +73,7 @@ describe('BrowserStorageService', () => { const config = { configId: 'configId1' }; vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); - const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')(); + const writeSpy = vi.spyOn(abstractSecurityStorage, 'write'); const result = service.write({ anyKey: 'anyvalue' }, config); @@ -92,7 +89,7 @@ describe('BrowserStorageService', () => { vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); - const writeSpy = vi.spyOn(abstractSecurityStorage, 'write')(); + const writeSpy = vi.spyOn(abstractSecurityStorage, 'write'); const somethingFalsy = ''; const result = service.write(somethingFalsy, config); @@ -117,7 +114,7 @@ describe('BrowserStorageService', () => { vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); const config = { configId: 'configId1' }; - const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove')(); + const setItemSpy = vi.spyOn(abstractSecurityStorage, 'remove'); const result = service.remove('anyKey', config); @@ -137,7 +134,7 @@ describe('BrowserStorageService', () => { it('returns true if clear is called', () => { vi.spyOn(service as any, 'hasStorage').mockReturnValue(true); - const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear')(); + const setItemSpy = vi.spyOn(abstractSecurityStorage, 'clear'); const config = { configId: 'configId1' }; const result = service.clear(config); @@ -149,8 +146,11 @@ describe('BrowserStorageService', () => { describe('hasStorage', () => { it('returns false if there is no storage', () => { + // biome-ignore lint/suspicious/noGlobalAssign: (Storage as any) = undefined; expect((service as any).hasStorage()).toBeFalsy(); + // biome-ignore lint/correctness/noSelfAssign: + // biome-ignore lint/suspicious/noGlobalAssign: Storage = Storage; }); }); diff --git a/src/storage/browser-storage.service.ts b/src/storage/browser-storage.service.ts index e8afdb9..3eac0e3 100644 --- a/src/storage/browser-storage.service.ts +++ b/src/storage/browser-storage.service.ts @@ -1,5 +1,5 @@ import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { LoggerService } from '../logging/logger.service'; import { AbstractSecurityStorage } from './abstract-security-storage'; @@ -54,7 +54,7 @@ export class BrowserStorageService { if (!this.hasStorage()) { this.loggerService.logDebug( configuration, - `Wanted to write but Storage was falsy` + 'Wanted to write but Storage was falsy' ); return false; @@ -94,7 +94,7 @@ export class BrowserStorageService { if (!this.hasStorage()) { this.loggerService.logDebug( configuration, - `Wanted to clear storage but Storage was falsy` + 'Wanted to clear storage but Storage was falsy' ); return false; diff --git a/src/storage/default-localstorage.service.spec.ts b/src/storage/default-localstorage.service.spec.ts index 5009d4a..37f9766 100644 --- a/src/storage/default-localstorage.service.spec.ts +++ b/src/storage/default-localstorage.service.spec.ts @@ -9,9 +9,6 @@ describe('DefaultLocalStorageService', () => { TestBed.configureTestingModule({ providers: [DefaultLocalStorageService], }); - }); - - beforeEach(() => { service = TestBed.inject(DefaultLocalStorageService); }); diff --git a/src/storage/default-localstorage.service.ts b/src/storage/default-localstorage.service.ts index 2fc9e82..f03ff7f 100644 --- a/src/storage/default-localstorage.service.ts +++ b/src/storage/default-localstorage.service.ts @@ -1,21 +1,21 @@ import { Injectable } from 'injection-js'; -import { AbstractSecurityStorage } from './abstract-security-storage'; +import type { AbstractSecurityStorage } from './abstract-security-storage'; @Injectable() export class DefaultLocalStorageService implements AbstractSecurityStorage { - public read(key: string): string | null { + read(key: string): string | null { return localStorage.getItem(key); } - public write(key: string, value: string): void { + write(key: string, value: string): void { localStorage.setItem(key, value); } - public remove(key: string): void { + remove(key: string): void { localStorage.removeItem(key); } - public clear(): void { + clear(): void { localStorage.clear(); } } diff --git a/src/storage/default-sessionstorage.service.spec.ts b/src/storage/default-sessionstorage.service.spec.ts index 6f904d7..f2eabfd 100644 --- a/src/storage/default-sessionstorage.service.spec.ts +++ b/src/storage/default-sessionstorage.service.spec.ts @@ -9,9 +9,6 @@ describe('DefaultSessionStorageService', () => { TestBed.configureTestingModule({ providers: [DefaultSessionStorageService], }); - }); - - beforeEach(() => { service = TestBed.inject(DefaultSessionStorageService); }); diff --git a/src/storage/default-sessionstorage.service.ts b/src/storage/default-sessionstorage.service.ts index 93dbbc5..57d22f6 100644 --- a/src/storage/default-sessionstorage.service.ts +++ b/src/storage/default-sessionstorage.service.ts @@ -1,21 +1,21 @@ import { Injectable } from 'injection-js'; -import { AbstractSecurityStorage } from './abstract-security-storage'; +import type { AbstractSecurityStorage } from './abstract-security-storage'; @Injectable() export class DefaultSessionStorageService implements AbstractSecurityStorage { - public read(key: string): string | null { + read(key: string): string | null { return sessionStorage.getItem(key); } - public write(key: string, value: string): void { + write(key: string, value: string): void { sessionStorage.setItem(key, value); } - public remove(key: string): void { + remove(key: string): void { sessionStorage.removeItem(key); } - public clear(): void { + clear(): void { sessionStorage.clear(); } } diff --git a/src/storage/storage-persistence.service.spec.ts b/src/storage/storage-persistence.service.spec.ts index 33f77c1..18ba47a 100644 --- a/src/storage/storage-persistence.service.spec.ts +++ b/src/storage/storage-persistence.service.spec.ts @@ -12,9 +12,6 @@ describe('Storage Persistence Service', () => { TestBed.configureTestingModule({ providers: [mockProvider(BrowserStorageService)], }); - }); - - beforeEach(() => { service = TestBed.inject(StoragePersistenceService); securityStorage = TestBed.inject(BrowserStorageService); }); diff --git a/src/testing/create-retriable-stream.helper.ts b/src/testing/create-retriable-stream.helper.ts index 6b339de..ce0c86f 100644 --- a/src/testing/create-retriable-stream.helper.ts +++ b/src/testing/create-retriable-stream.helper.ts @@ -1,10 +1,11 @@ import { type Observable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; +import { vi } from 'vitest'; // Create retriable observable stream to test retry / retryWhen. Credits to: // https://stackoverflow.com/questions/51399819/how-to-create-a-mock-observable-to-test-http-rxjs-retry-retrywhen-in-angular export const createRetriableStream = (...resp$: any): Observable => { - const fetchData: jasmine.Spy = jasmine.createSpy('fetchData'); + const fetchData = vi.fn()('fetchData'); fetchData.mockReturnValues(...resp$); diff --git a/src/user-data/user-service.spec.ts b/src/user-data/user-service.spec.ts index f525ade..22860e7 100644 --- a/src/user-data/user-service.spec.ts +++ b/src/user-data/user-service.spec.ts @@ -50,7 +50,7 @@ describe('User Service', () => { expect(userService).toBeTruthy(); }); - it('public authorize$ is observable$', () => { + it('authorize$ is observable$', () => { expect(userService.userData$).toBeInstanceOf(Observable); }); @@ -225,6 +225,7 @@ describe('User Service', () => { const idToken = ''; const decodedIdToken = { sub: 'decodedIdToken' }; const userDataInstore = ''; + // biome-ignore lint/suspicious/noEvolvingTypes: const userDataFromSts = null; const config = { diff --git a/src/utils/crypto/crypto.service.spec.ts b/src/utils/crypto/crypto.service.spec.ts index 5f1f6cb..0576527 100644 --- a/src/utils/crypto/crypto.service.spec.ts +++ b/src/utils/crypto/crypto.service.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; import { DOCUMENT } from '../../dom'; import { CryptoService } from './crypto.service'; @@ -16,9 +15,6 @@ describe('CryptoService', () => { }, ], }); - }); - - beforeEach(() => { cryptoService = TestBed.inject(CryptoService); }); @@ -50,9 +46,6 @@ describe('CryptoService: msCrypto', () => { }, ], }); - }); - - beforeEach(() => { cryptoService = TestBed.inject(CryptoService); }); diff --git a/src/utils/equality/equality.service.spec.ts b/src/utils/equality/equality.service.spec.ts index a074c9b..9498a5b 100644 --- a/src/utils/equality/equality.service.spec.ts +++ b/src/utils/equality/equality.service.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; import { IFrameService } from '../../iframe/existing-iframe.service'; import { EqualityService } from './equality.service'; @@ -10,9 +9,6 @@ describe('EqualityService Tests', () => { TestBed.configureTestingModule({ providers: [EqualityService, IFrameService], }); - }); - - beforeEach(() => { equalityHelperService = TestBed.inject(EqualityService); }); diff --git a/src/utils/equality/equality.service.ts b/src/utils/equality/equality.service.ts index 09e712f..7706340 100644 --- a/src/utils/equality/equality.service.ts +++ b/src/utils/equality/equality.service.ts @@ -99,7 +99,7 @@ export class EqualityService { return typeof value === 'object'; } - private arraysStrictEqual(arr1: Array, arr2: Array): boolean { + private arraysStrictEqual(arr1: string[], arr2: string[]): boolean { if (arr1.length !== arr2.length) { return false; } @@ -113,10 +113,7 @@ export class EqualityService { return true; } - private arraysHaveEqualContent( - arr1: Array, - arr2: Array - ): boolean { + private arraysHaveEqualContent(arr1: string[], arr2: string[]): boolean { if (arr1.length !== arr2.length) { return false; } diff --git a/src/utils/flowHelper/flow-helper.service.spec.ts b/src/utils/flowHelper/flow-helper.service.spec.ts index b294924..0e15243 100644 --- a/src/utils/flowHelper/flow-helper.service.spec.ts +++ b/src/utils/flowHelper/flow-helper.service.spec.ts @@ -9,9 +9,6 @@ describe('Flow Helper Service', () => { TestBed.configureTestingModule({ providers: [FlowHelper], }); - }); - - beforeEach(() => { flowHelper = TestBed.inject(FlowHelper); }); diff --git a/src/utils/platform-provider/platform-provider.spec.ts b/src/utils/platform-provider/platform-provider.spec.ts index 1ad3fea..f15e124 100644 --- a/src/utils/platform-provider/platform-provider.spec.ts +++ b/src/utils/platform-provider/platform-provider.spec.ts @@ -1,6 +1,5 @@ import { TestBed } from '@/testing'; import { PLATFORM_ID } from '@angular/core'; -import { vi } from 'vitest'; import { PlatformProvider } from './platform.provider'; describe('PlatformProvider Tests', () => { diff --git a/src/utils/platform-provider/platform.provider.ts b/src/utils/platform-provider/platform.provider.ts index d7c1645..79611b4 100644 --- a/src/utils/platform-provider/platform.provider.ts +++ b/src/utils/platform-provider/platform.provider.ts @@ -1,11 +1,14 @@ -import { isPlatformBrowser } from '@angular/common'; -import { Injectable, PLATFORM_ID, inject } from 'injection-js'; +import { Injectable, InjectionToken, inject } from 'injection-js'; + +export type PlatformId = 'browser' | 'server'; + +export const PLATFORM_ID = new InjectionToken('PLATFORM_ID'); @Injectable() export class PlatformProvider { private readonly platformId = inject(PLATFORM_ID); isBrowser(): boolean { - return isPlatformBrowser(this.platformId); + return this.platformId === 'browser'; } } diff --git a/src/utils/redirect/redirect.service.spec.ts b/src/utils/redirect/redirect.service.spec.ts index f0ea098..7e2a717 100644 --- a/src/utils/redirect/redirect.service.spec.ts +++ b/src/utils/redirect/redirect.service.spec.ts @@ -1,5 +1,4 @@ -import { TestBed } from '@/testing'; -import { vi } from 'vitest'; +import { TestBed, spyOnProperty } from '@/testing'; import { DOCUMENT } from '../../dom'; import { RedirectService } from './redirect.service'; @@ -26,9 +25,6 @@ describe('Redirect Service Tests', () => { }, ], }); - }); - - beforeEach(() => { service = TestBed.inject(RedirectService); myDocument = TestBed.inject(DOCUMENT); }); diff --git a/src/utils/tokenHelper/token-helper.service.spec.ts b/src/utils/tokenHelper/token-helper.service.spec.ts index 437aff5..b27ba45 100644 --- a/src/utils/tokenHelper/token-helper.service.spec.ts +++ b/src/utils/tokenHelper/token-helper.service.spec.ts @@ -1,5 +1,4 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; import { LoggerService } from '../../logging/logger.service'; import { mockProvider } from '../../testing/mock'; import { TokenHelperService } from './token-helper.service'; @@ -11,9 +10,6 @@ describe('Token Helper Service', () => { TestBed.configureTestingModule({ providers: [mockProvider(LoggerService)], }); - }); - - beforeEach(() => { tokenHelperService = TestBed.inject(TokenHelperService); }); @@ -215,7 +211,7 @@ describe('Token Helper Service', () => { configId: 'configId1', }); - expect(result).toEqual(jasmine.objectContaining(expected)); + expect(result).toEqual(expect.objectContaining(expected)); }); }); diff --git a/src/utils/tokenHelper/token-helper.service.ts b/src/utils/tokenHelper/token-helper.service.ts index 31ad11c..d57de07 100644 --- a/src/utils/tokenHelper/token-helper.service.ts +++ b/src/utils/tokenHelper/token-helper.service.ts @@ -1,6 +1,6 @@ +import { Injectable, inject } from 'injection-js'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { DOCUMENT } from '../../dom'; -import { inject, Injectable } from 'injection-js'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; import { LoggerService } from '../../logging/logger.service'; const PARTS_OF_TOKEN = 3; @@ -111,7 +111,7 @@ export class TokenHelperService { output += '='; break; default: - throw Error('Illegal base64url string!'); + throw new Error('Illegal base64url string!'); } const decoded = @@ -129,11 +129,11 @@ export class TokenHelperService { decoded .split('') .map( - (c: string) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + (c: string) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}` ) .join('') ); - } catch (err) { + } catch { return decoded; } } diff --git a/src/utils/url/uri-encoder.ts b/src/utils/url/uri-encoder.ts index 3fd2168..85dcbdf 100644 --- a/src/utils/url/uri-encoder.ts +++ b/src/utils/url/uri-encoder.ts @@ -1,4 +1,4 @@ -import { HttpParameterCodec } from '@ngify/http'; +import type { HttpParameterCodec } from '@ngify/http'; export class UriEncoder implements HttpParameterCodec { encodeKey(key: string): string { diff --git a/src/utils/url/url.service.ts b/src/utils/url/url.service.ts index a1a71bd..823a6a6 100644 --- a/src/utils/url/url.service.ts +++ b/src/utils/url/url.service.ts @@ -1,9 +1,9 @@ import { HttpParams } from '@ngify/http'; -import { inject, Injectable } from 'injection-js'; -import { Observable, of } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { AuthOptions } from '../../auth-options'; -import { OpenIdConfiguration } from '../../config/openid-configuration'; +import type { AuthOptions } from '../../auth-options'; +import type { OpenIdConfiguration } from '../../config/openid-configuration'; import { FlowsDataService } from '../../flows/flows-data.service'; import { LoggerService } from '../../logging/logger.service'; import { StoragePersistenceService } from '../../storage/storage-persistence.service'; @@ -37,8 +37,9 @@ export class UrlService { return ''; } + // biome-ignore lint/performance/useTopLevelRegex: name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); - const regex = new RegExp('[\\?&#]' + name + '=([^&#]*)'); + const regex = new RegExp(`[\\?&#]${name}=([^&#]*)`); const results = regex.exec(urlToCheck); return results === null ? '' : decodeURIComponent(results[1]); @@ -47,9 +48,10 @@ export class UrlService { getUrlWithoutQueryParameters(url: URL): URL { const u = new URL(url.toString()); + // biome-ignore lint/suspicious/noEvolvingTypes: const keys = []; - for (const key of u.searchParams.keys()) { + for (const key of Array.from(u.searchParams.keys())) { keys.push(key); } @@ -60,10 +62,13 @@ export class UrlService { return u; } - queryParametersExist(expected: URLSearchParams, actual: URLSearchParams): boolean { + queryParametersExist( + expected: URLSearchParams, + actual: URLSearchParams + ): boolean { let r = true; - expected.forEach((v, k) => { + expected.forEach((_v, k) => { if (!actual.has(k)) { r = false; } @@ -73,7 +78,7 @@ export class UrlService { } isCallbackFromSts(currentUrl: string, config?: OpenIdConfiguration): boolean { - if (config && config.checkRedirectUrlWhenCheckingIfIsCallback) { + if (config?.checkRedirectUrlWhenCheckingIfIsCallback) { const currentUrlInstance = new URL(currentUrl); const redirectUrl = this.getRedirectUrl(config); @@ -81,7 +86,7 @@ export class UrlService { if (!redirectUrl) { this.loggerService.logError( config, - `UrlService.isCallbackFromSts: could not get redirectUrl from config, was: `, + 'UrlService.isCallbackFromSts: could not get redirectUrl from config, was: ', redirectUrl ); @@ -164,7 +169,7 @@ export class UrlService { if (!clientId) { this.loggerService.logError( configuration, - `getAuthorizeParUrl could not add clientId because it was: `, + 'getAuthorizeParUrl could not add clientId because it was: ', clientId ); @@ -315,7 +320,7 @@ export class UrlService { if (!codeVerifier) { this.loggerService.logError( configuration, - `CodeVerifier is not set `, + 'CodeVerifier is not set ', codeVerifier ); @@ -393,7 +398,7 @@ export class UrlService { this.loggerService.logDebug( configuration, - 'Authorize created. adding myautostate: ' + state + `Authorize created. adding myautostate: ${state}` ); // code_challenge with "S256" @@ -449,7 +454,7 @@ export class UrlService { if (!postLogoutRedirectUri) { this.loggerService.logError( configuration, - `could not get postLogoutRedirectUri, was: `, + 'could not get postLogoutRedirectUri, was: ', postLogoutRedirectUri ); @@ -479,7 +484,7 @@ export class UrlService { let params = this.createHttpParams(existingParams); - if (!!idTokenHint) { + if (idTokenHint) { params = params.set('id_token_hint', idTokenHint); } @@ -526,7 +531,7 @@ export class UrlService { if (!clientId) { this.loggerService.logError( configuration, - `createAuthorizeUrl could not add clientId because it was: `, + 'createAuthorizeUrl could not add clientId because it was: ', clientId ); @@ -536,7 +541,7 @@ export class UrlService { if (!responseType) { this.loggerService.logError( configuration, - `createAuthorizeUrl could not add responseType because it was: `, + 'createAuthorizeUrl could not add responseType because it was: ', responseType ); @@ -546,7 +551,7 @@ export class UrlService { if (!scope) { this.loggerService.logError( configuration, - `createAuthorizeUrl could not add scope because it was: `, + 'createAuthorizeUrl could not add scope because it was: ', scope ); @@ -641,7 +646,7 @@ export class UrlService { this.loggerService.logDebug( configuration, - 'RefreshSession created. adding myautostate: ' + state + `RefreshSession created. adding myautostate: ${state}` ); // code_challenge with "S256" @@ -693,7 +698,7 @@ export class UrlService { this.loggerService.logDebug( configuration, - 'Authorize created. adding myautostate: ' + state + `Authorize created. adding myautostate: ${state}` ); const redirectUrl = this.getRedirectUrl(configuration, authOptions); @@ -739,7 +744,7 @@ export class UrlService { this.loggerService.logDebug( config, - 'Authorize created. adding myautostate: ' + state + `Authorize created. adding myautostate: ${state}` ); const redirectUrl = this.getRedirectUrl(config, authOptions); @@ -804,7 +809,7 @@ export class UrlService { if (!redirectUrl) { this.loggerService.logError( configuration, - `could not get redirectUrl, was: `, + 'could not get redirectUrl, was: ', redirectUrl ); @@ -820,7 +825,7 @@ export class UrlService { if (!silentRenewUrl) { this.loggerService.logError( configuration, - `could not get silentRenewUrl, was: `, + 'could not get silentRenewUrl, was: ', silentRenewUrl ); @@ -836,7 +841,7 @@ export class UrlService { if (!clientId) { this.loggerService.logError( configuration, - `could not get clientId, was: `, + 'could not get clientId, was: ', clientId ); diff --git a/src/validation/jwt-window-crypto.service.spec.ts b/src/validation/jwt-window-crypto.service.spec.ts index 1e74c01..1654867 100644 --- a/src/validation/jwt-window-crypto.service.spec.ts +++ b/src/validation/jwt-window-crypto.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@/testing'; -import { vi } from 'vitest'; +import { lastValueFrom } from 'rxjs'; import { CryptoService } from '../utils/crypto/crypto.service'; import { JwtWindowCryptoService } from './jwt-window-crypto.service'; @@ -11,9 +11,6 @@ describe('JwtWindowCryptoService', () => { imports: [], providers: [JwtWindowCryptoService, CryptoService], }); - }); - - beforeEach(() => { service = TestBed.inject(JwtWindowCryptoService); }); @@ -29,7 +26,7 @@ describe('JwtWindowCryptoService', () => { ); const value = await lastValueFrom(observable); -expect(value).toBe(outcome); + expect(value).toBe(outcome); }); }); }); diff --git a/src/validation/jwt-window-crypto.service.ts b/src/validation/jwt-window-crypto.service.ts index 34a6abf..4373d6f 100644 --- a/src/validation/jwt-window-crypto.service.ts +++ b/src/validation/jwt-window-crypto.service.ts @@ -1,5 +1,5 @@ import { inject, Injectable } from 'injection-js'; -import { from, Observable } from 'rxjs'; +import { from, type Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { CryptoService } from '../utils/crypto/crypto.service'; diff --git a/src/validation/state-validation-result.ts b/src/validation/state-validation-result.ts index 224cb39..6146fee 100644 --- a/src/validation/state-validation-result.ts +++ b/src/validation/state-validation-result.ts @@ -2,12 +2,22 @@ import { ValidationResult } from './validation-result'; export class StateValidationResult { constructor( + // biome-ignore lint/style/noParameterProperties: + // biome-ignore lint/nursery/useConsistentMemberAccessibility: public accessToken = '', + // biome-ignore lint/style/noParameterProperties: + // biome-ignore lint/nursery/useConsistentMemberAccessibility: public idToken = '', + // biome-ignore lint/style/noParameterProperties: + // biome-ignore lint/nursery/useConsistentMemberAccessibility: public authResponseIsValid = false, + // biome-ignore lint/style/noParameterProperties: + // biome-ignore lint/nursery/useConsistentMemberAccessibility: public decodedIdToken: any = { at_hash: '', }, + // biome-ignore lint/style/noParameterProperties: + // biome-ignore lint/nursery/useConsistentMemberAccessibility: public state: ValidationResult = ValidationResult.NotSet ) {} } diff --git a/src/validation/state-validation.service.spec.ts b/src/validation/state-validation.service.spec.ts index 6e5b906..34f6faf 100644 --- a/src/validation/state-validation.service.spec.ts +++ b/src/validation/state-validation.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from '@/testing'; -import { of } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { vi } from 'vitest'; import type { AuthWellKnownEndpoints } from '../config/auth-well-known/auth-well-known-endpoints'; import type { OpenIdConfiguration } from '../config/openid-configuration'; @@ -36,18 +36,12 @@ describe('State Validation Service', () => { FlowHelper, ], }); - }); - - beforeEach(() => { stateValidationService = TestBed.inject(StateValidationService); tokenValidationService = TestBed.inject(TokenValidationService); tokenHelperService = TestBed.inject(TokenHelperService); loggerService = TestBed.inject(LoggerService); storagePersistenceService = TestBed.inject(StoragePersistenceService); flowHelper = TestBed.inject(FlowHelper); - }); - - beforeEach(() => { config = { authority: 'https://localhost:44363', redirectUrl: 'https://localhost:44363', @@ -694,7 +688,7 @@ describe('State Validation Service', () => { ); const isValid = await lastValueFrom(isValidObs$); -expect(isValid.authResponseIsValid).toBe(false); + expect(isValid.authResponseIsValid).toBe(false); }); it('should return invalid context error', async () => { @@ -730,7 +724,7 @@ expect(isValid.authResponseIsValid).toBe(false); ); const isValid = await lastValueFrom(isValidObs$); -expect(isValid.authResponseIsValid).toBe(false); + expect(isValid.authResponseIsValid).toBe(false); }); it('should return invalid result if validateIdTokenExpNotExpired is false', async () => { @@ -825,14 +819,14 @@ expect(isValid.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback id token expired' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback id token expired' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateStateFromHashCallback is false', async () => { @@ -877,14 +871,14 @@ expect(state.authResponseIsValid).toBe(false); ).toHaveBeenCalled(); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback incorrect state' - );; -expect(state.accessToken).toBe('');; -expect(state.authResponseIsValid).toBe(false);; -expect(state.decodedIdToken).toBeDefined();; -expect(state.idToken).toBe(''); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback incorrect state' + ); + expect(state.accessToken).toBe(''); + expect(state.authResponseIsValid).toBe(false); + expect(state.decodedIdToken).toBeDefined(); + expect(state.idToken).toBe(''); }); it('access_token should equal result.access_token and is valid if response_type is "id_token token"', async () => { @@ -974,10 +968,10 @@ expect(state.idToken).toBe(''); ); const state = await lastValueFrom(stateObs$); -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(true); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(true); }); it('should return invalid result if validateSignatureIdToken is false', async () => { @@ -1027,14 +1021,14 @@ expect(state.authResponseIsValid).toBe(true); ); const state = await lastValueFrom(stateObs$); -expect(logDebugSpy).toBeCalledWith([ - [config, 'authCallback Signature validation failed id_token'], - [config, 'authCallback token(s) invalid'], - ]);; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logDebugSpy).toBeCalledWith([ + [config, 'authCallback Signature validation failed id_token'], + [config, 'authCallback token(s) invalid'], + ]); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateIdTokenNonce is false', async () => { @@ -1087,14 +1081,14 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback incorrect nonce, did you call the checkAuth() method multiple times?' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback incorrect nonce, did you call the checkAuth() method multiple times?' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateRequiredIdToken is false', async () => { @@ -1155,18 +1149,18 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logDebugSpy).toHaveBeenCalledWith( - config, - 'authCallback Validation, one of the REQUIRED properties missing from id_token' - );; -expect(logDebugSpy).toHaveBeenCalledWith( - config, - 'authCallback token(s) invalid' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logDebugSpy).toHaveBeenCalledWith( + config, + 'authCallback Validation, one of the REQUIRED properties missing from id_token' + ); + expect(logDebugSpy).toHaveBeenCalledWith( + config, + 'authCallback token(s) invalid' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateIdTokenIatMaxOffset is false', async () => { @@ -1230,14 +1224,14 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback Validation, iat rejected id_token was issued too far away from the current time' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback Validation, iat rejected id_token was issued too far away from the current time' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateIdTokenIss is false and has authWellKnownEndPoints', async () => { @@ -1308,14 +1302,14 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback incorrect iss does not match authWellKnownEndpoints issuer' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback incorrect iss does not match authWellKnownEndpoints issuer' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateIdTokenIss is false and has no authWellKnownEndPoints', async () => { @@ -1374,15 +1368,15 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authWellKnownEndpoints is undefined' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false);; -expect(state.state).toBe(ValidationResult.NoAuthWellKnownEndPoints); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authWellKnownEndpoints is undefined' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); + expect(state.state).toBe(ValidationResult.NoAuthWellKnownEndPoints); }); it('should return invalid result if validateIdTokenAud is false', async () => { @@ -1451,14 +1445,14 @@ expect(state.state).toBe(ValidationResult.NoAuthWellKnownEndPoints); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback incorrect aud' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback incorrect aud' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return invalid result if validateIdTokenAzpExistsIfMoreThanOneAud is false', async () => { @@ -1531,15 +1525,15 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback missing azp' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false);; -expect(state.state).toBe(ValidationResult.IncorrectAzp); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback missing azp' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); + expect(state.state).toBe(ValidationResult.IncorrectAzp); }); it('should return invalid result if validateIdTokenAzpValid is false', async () => { @@ -1616,15 +1610,15 @@ expect(state.state).toBe(ValidationResult.IncorrectAzp); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback incorrect azp' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false);; -expect(state.state).toBe(ValidationResult.IncorrectAzp); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback incorrect azp' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); + expect(state.state).toBe(ValidationResult.IncorrectAzp); }); it('should return invalid result if isIdTokenAfterRefreshTokenRequestValid is false', async () => { @@ -1705,17 +1699,17 @@ expect(state.state).toBe(ValidationResult.IncorrectAzp); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback pre, post id_token claims do not match in refresh' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false);; -expect(state.state).toBe( - ValidationResult.IncorrectIdTokenClaimsAfterRefresh - ); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback pre, post id_token claims do not match in refresh' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); + expect(state.state).toBe( + ValidationResult.IncorrectIdTokenClaimsAfterRefresh + ); }); it('Reponse is valid if authConfiguration.response_type does not equal "id_token token"', async () => { @@ -1808,18 +1802,18 @@ expect(state.state).toBe( ); const state = await lastValueFrom(stateObs$); -expect(logDebugSpy).toHaveBeenCalledWith( - config, - 'authCallback token(s) validated, continue' - );; -expect(logDebugSpy).toHaveBeenCalledWith( - config, - 'authCallback token(s) invalid' - );; -expect(state.accessToken).toBe('');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(true); + expect(logDebugSpy).toHaveBeenCalledWith( + config, + 'authCallback token(s) validated, continue' + ); + expect(logDebugSpy).toHaveBeenCalledWith( + config, + 'authCallback token(s) invalid' + ); + expect(state.accessToken).toBe(''); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(true); }); it('Response is invalid if validateIdTokenAtHash is false', async () => { @@ -1913,14 +1907,14 @@ expect(state.authResponseIsValid).toBe(true); ); const state = await lastValueFrom(stateObs$); -expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( - config, - 'authCallback incorrect at_hash' - );; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('id_tokenTEST');; -expect(state.decodedIdToken).toBe('decoded_id_token');; -expect(state.authResponseIsValid).toBe(false); + expect(logWarningSpy).toHaveBeenCalledExactlyOnceWith( + config, + 'authCallback incorrect at_hash' + ); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe('id_tokenTEST'); + expect(state.decodedIdToken).toBe('decoded_id_token'); + expect(state.authResponseIsValid).toBe(false); }); it('should return valid result if validateIdTokenIss is false and iss_validation_off is true', async () => { @@ -2010,15 +2004,15 @@ expect(state.authResponseIsValid).toBe(false); ); const state = await lastValueFrom(stateObs$); -expect(logDebugSpy).toBeCalledWith([ - [config, 'iss validation is turned off, this is not recommended!'], - [config, 'authCallback token(s) validated, continue'], - ]);; -expect(state.state).toBe(ValidationResult.Ok);; -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.authResponseIsValid).toBe(true);; -expect(state.decodedIdToken).toBeDefined();; -expect(state.idToken).toBe('id_tokenTEST'); + expect(logDebugSpy).toBeCalledWith([ + [config, 'iss validation is turned off, this is not recommended!'], + [config, 'authCallback token(s) validated, continue'], + ]); + expect(state.state).toBe(ValidationResult.Ok); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.authResponseIsValid).toBe(true); + expect(state.decodedIdToken).toBeDefined(); + expect(state.idToken).toBe('id_tokenTEST'); }); it('should return valid if there is no id_token', async () => { @@ -2095,10 +2089,10 @@ expect(state.idToken).toBe('id_tokenTEST'); ); const state = await lastValueFrom(stateObs$); -expect(state.accessToken).toBe('access_tokenTEST');; -expect(state.idToken).toBe('');; -expect(state.decodedIdToken).toBeDefined();; -expect(state.authResponseIsValid).toBe(true); + expect(state.accessToken).toBe('access_tokenTEST'); + expect(state.idToken).toBe(''); + expect(state.decodedIdToken).toBeDefined(); + expect(state.authResponseIsValid).toBe(true); }); it('should return OK if disableIdTokenValidation is true', async () => { @@ -2134,8 +2128,8 @@ expect(state.authResponseIsValid).toBe(true); ); const isValid = await lastValueFrom(isValidObs$); -expect(isValid.state).toBe(ValidationResult.Ok);; -expect(isValid.authResponseIsValid).toBe(true); + expect(isValid.state).toBe(ValidationResult.Ok); + expect(isValid.authResponseIsValid).toBe(true); }); it('should return OK if disableIdTokenValidation is true', async () => { @@ -2171,8 +2165,8 @@ expect(isValid.authResponseIsValid).toBe(true); ); const isValid = await lastValueFrom(isValidObs$); -expect(isValid.state).toBe(ValidationResult.Ok);; -expect(isValid.authResponseIsValid).toBe(true); + expect(isValid.state).toBe(ValidationResult.Ok); + expect(isValid.authResponseIsValid).toBe(true); }); it('should return OK if disableIdTokenValidation is false but inrefreshtokenflow and no id token is returned', async () => { @@ -2208,8 +2202,8 @@ expect(isValid.authResponseIsValid).toBe(true); ); const isValid = await lastValueFrom(isValidObs$); -expect(isValid.state).toBe(ValidationResult.Ok);; -expect(isValid.authResponseIsValid).toBe(true); + expect(isValid.state).toBe(ValidationResult.Ok); + expect(isValid.authResponseIsValid).toBe(true); }); }); }); diff --git a/src/validation/state-validation.service.ts b/src/validation/state-validation.service.ts index 318c519..19839db 100644 --- a/src/validation/state-validation.service.ts +++ b/src/validation/state-validation.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from 'injection-js'; -import { Observable, of } from 'rxjs'; +import { Injectable, inject } from 'injection-js'; +import { type Observable, of } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; -import { CallbackContext } from '../flows/callback-context'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; +import type { CallbackContext } from '../flows/callback-context'; import { LoggerService } from '../logging/logger.service'; import { StoragePersistenceService } from '../storage/storage-persistence.service'; import { EqualityService } from '../utils/equality/equality.service'; @@ -122,6 +122,7 @@ export class StateValidationService { configuration ) .pipe( + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: mergeMap((isSignatureIdTokenValid: boolean) => { if (!isSignatureIdTokenValid) { this.loggerService.logDebug( @@ -324,12 +325,11 @@ export class StateValidationService { ); }) ); - } else { - this.loggerService.logDebug( - configuration, - 'No id_token found, skipping id_token validation' - ); } + this.loggerService.logDebug( + configuration, + 'No id_token found, skipping id_token validation' + ); return this.validateDefault( isCurrentFlowImplicitFlowWithAccessToken, @@ -391,14 +391,13 @@ export class StateValidationService { toReturn.state = ValidationResult.IncorrectAtHash; this.handleUnsuccessfulValidation(configuration); - return toReturn; - } else { - toReturn.authResponseIsValid = true; - toReturn.state = ValidationResult.Ok; - this.handleSuccessfulValidation(configuration); - return toReturn; } + toReturn.authResponseIsValid = true; + toReturn.state = ValidationResult.Ok; + this.handleSuccessfulValidation(configuration); + + return toReturn; }) ); } diff --git a/src/validation/token-validation.helper.ts b/src/validation/token-validation.helper.ts index 4bc993c..dae4053 100644 --- a/src/validation/token-validation.helper.ts +++ b/src/validation/token-validation.helper.ts @@ -7,20 +7,21 @@ export function getVerifyAlg( name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', }; - case 'E': + case 'E': { if (alg.includes('256')) { return { name: 'ECDSA', hash: 'SHA-256', }; - } else if (alg.includes('384')) { + } + if (alg.includes('384')) { return { name: 'ECDSA', hash: 'SHA-384', }; - } else { - return null; } + return null; + } default: return null; } @@ -35,7 +36,7 @@ export function alg2kty(alg: string): string { return 'EC'; default: - throw new Error('Cannot infer kty from alg: ' + alg); + throw new Error(`Cannot infer kty from alg: ${alg}`); } } @@ -43,39 +44,42 @@ export function getImportAlg( alg: string ): RsaHashedImportParams | EcKeyImportParams | null { switch (alg.charAt(0)) { - case 'R': + case 'R': { if (alg.includes('256')) { return { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256', }; - } else if (alg.includes('384')) { + } + if (alg.includes('384')) { return { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-384', }; - } else if (alg.includes('512')) { + } + if (alg.includes('512')) { return { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-512', }; - } else { - return null; } - case 'E': + return null; + } + case 'E': { if (alg.includes('256')) { return { name: 'ECDSA', namedCurve: 'P-256', }; - } else if (alg.includes('384')) { + } + if (alg.includes('384')) { return { name: 'ECDSA', namedCurve: 'P-384', }; - } else { - return null; } + return null; + } default: return null; } diff --git a/src/validation/token-validation.service.ts b/src/validation/token-validation.service.ts index b07135f..eebb6d5 100644 --- a/src/validation/token-validation.service.ts +++ b/src/validation/token-validation.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from 'injection-js'; +import { Injectable, inject } from 'injection-js'; import { base64url } from 'rfc4648'; -import { from, Observable, of } from 'rxjs'; +import { type Observable, from, of } from 'rxjs'; import { map, mergeMap, tap } from 'rxjs/operators'; -import { OpenIdConfiguration } from '../config/openid-configuration'; +import type { OpenIdConfiguration } from '../config/openid-configuration'; import { JwkExtractor } from '../extractors/jwk.extractor'; import { LoggerService } from '../logging/logger.service'; import { TokenHelperService } from '../utils/tokenHelper/token-helper.service'; @@ -257,6 +257,7 @@ export class TokenValidationService { const dateTimeIatIdToken = new Date(0); // The 0 here is the key, which sets the date to the epoch dateTimeIatIdToken.setUTCSeconds(dataIdToken.iat); + maxOffsetAllowedInSeconds = maxOffsetAllowedInSeconds || 0; const nowInUtc = new Date(new Date().toUTCString()); @@ -295,10 +296,7 @@ export class TokenValidationService { if (!isFromRefreshToken && dataIdToken.nonce !== localNonce) { this.loggerService.logDebug( configuration, - 'Validate_id_token_nonce failed, dataIdToken.nonce: ' + - dataIdToken.nonce + - ' local_nonce:' + - localNonce + `Validate_id_token_nonce failed, dataIdToken.nonce: ${dataIdToken.nonce} local_nonce:${localNonce}` ); return false; @@ -319,10 +317,7 @@ export class TokenValidationService { ) { this.loggerService.logDebug( configuration, - 'Validate_id_token_iss failed, dataIdToken.iss: ' + - dataIdToken.iss + - ' authWellKnownEndpoints issuer:' + - authWellKnownEndpointsIssuer + `Validate_id_token_iss failed, dataIdToken.iss: ${dataIdToken.iss} authWellKnownEndpoints issuer:${authWellKnownEndpointsIssuer}` ); return false; @@ -346,23 +341,18 @@ export class TokenValidationService { if (!result) { this.loggerService.logDebug( configuration, - 'Validate_id_token_aud array failed, dataIdToken.aud: ' + - dataIdToken.aud + - ' client_id:' + - aud + `Validate_id_token_aud array failed, dataIdToken.aud: ${dataIdToken.aud} client_id:${aud}` ); return false; } return true; - } else if (dataIdToken.aud !== aud) { + } + if (dataIdToken.aud !== aud) { this.loggerService.logDebug( configuration, - 'Validate_id_token_aud failed, dataIdToken.aud: ' + - dataIdToken.aud + - ' client_id:' + - aud + `Validate_id_token_aud failed, dataIdToken.aud: ${dataIdToken.aud} client_id:${aud}` ); return false; @@ -403,10 +393,7 @@ export class TokenValidationService { if ((state as string) !== (localState as string)) { this.loggerService.logDebug( configuration, - 'ValidateStateFromHashCallback failed, state: ' + - state + - ' local_state:' + - localState + `ValidateStateFromHashCallback failed, state: ${state} local_state:${localState}` ); return false; @@ -555,7 +542,7 @@ export class TokenValidationService { ): Observable { this.loggerService.logDebug( configuration, - 'at_hash from the server:' + atHash + `at_hash from the server:${atHash}` ); // 'sha256' 'sha384' 'sha512' @@ -568,29 +555,28 @@ export class TokenValidationService { } return this.jwtWindowCryptoService - .generateAtHash('' + accessToken, sha) + .generateAtHash(`${accessToken}`, sha) .pipe( mergeMap((hash: string) => { this.loggerService.logDebug( configuration, - 'at_hash client validation not decoded:' + hash + `at_hash client validation not decoded:${hash}` ); if (hash === atHash) { return of(true); // isValid; - } else { - return this.jwtWindowCryptoService - .generateAtHash('' + decodeURIComponent(accessToken), sha) - .pipe( - map((newHash: string) => { - this.loggerService.logDebug( - configuration, - '-gen access--' + hash - ); - - return newHash === atHash; - }) - ); } + return this.jwtWindowCryptoService + .generateAtHash(`${decodeURIComponent(accessToken)}`, sha) + .pipe( + map((newHash: string) => { + this.loggerService.logDebug( + configuration, + `-gen access--${hash}` + ); + + return newHash === atHash; + }) + ); }) ); } @@ -599,7 +585,7 @@ export class TokenValidationService { const minutes = Math.floor(millis / 60000); const seconds = ((millis % 60000) / 1000).toFixed(0); - return minutes + ':' + (+seconds < 10 ? '0' : '') + seconds; + return `${minutes}:${+seconds < 10 ? '0' : ''}${seconds}`; } private calculateNowWithOffset(offsetSeconds: number): number {