fix: fix all biome

This commit is contained in:
master 2025-01-31 05:57:51 +08:00
parent 316361bd3c
commit c9d0066d64
114 changed files with 2255 additions and 2068 deletions

View File

@ -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"
}
}
},

View File

@ -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
);
});
});

View File

@ -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: <explanation>
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);
}

View File

@ -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)}`
);
}

View File

@ -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<T>(url: string, params?: { [key: string]: unknown }): Observable<T> {

View File

@ -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: <explanation>
const authResult = null;
authStateService.setAuthorizationData(

View File

@ -257,9 +257,8 @@ export class AuthStateService {
private decodeURIComponentSafely(token: string): string {
if (token) {
return decodeURIComponent(token);
} else {
return '';
}
return '';
}
private persistAccessTokenExpirationTime(

View File

@ -1,4 +1,4 @@
import { ValidationResult } from '../validation/validation-result';
import type { ValidationResult } from '../validation/validation-result';
export interface AuthStateResult {
isAuthenticated: boolean;

View File

@ -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'
);
}
});
});
});

View File

@ -57,7 +57,7 @@ export class CheckAuthService {
const stateParamFromUrl =
this.currentUrlService.getStateParamFromCurrentUrl(url);
return Boolean(stateParamFromUrl)
return stateParamFromUrl
? this.getConfigurationWithUrlState([configuration], stateParamFromUrl)
: configuration;
}

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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';

View File

@ -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');
}
});
});
});

View File

@ -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');
}
});
});
});

View File

@ -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();

View File

@ -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();
});

View File

@ -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', () => {

View File

@ -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));
});
});
});

View File

@ -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 {

View File

@ -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
);
}
});
});
});

View File

@ -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 {

View File

@ -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();
});

View File

@ -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',

View File

@ -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');
});
});
});

View File

@ -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;
}

View File

@ -1,4 +1,4 @@
import { OpenIdConfiguration } from '../openid-configuration';
import type { OpenIdConfiguration } from '../openid-configuration';
export interface Rule {
validate(passedConfig: OpenIdConfiguration): RuleValidationResult;

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
});

View File

@ -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 = {

View File

@ -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;

View File

@ -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);
}
});
});
});

View File

@ -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';

View File

@ -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();
}
});
});

View File

@ -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);

View File

@ -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);
});
});
});

View File

@ -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();
}
});
});
});

View File

@ -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));
}
}

View File

@ -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);
}
});
});
});

View File

@ -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()

View File

@ -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,
});
}
});
});
});

View File

@ -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);
})
);
}

View File

@ -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'
);
}
});
});
});

View File

@ -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: <explanation>
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}`;

View File

@ -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: <explanation>
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',

View File

@ -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();
});
});
});

View File

@ -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);
});

View File

@ -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 {

View File

@ -23,9 +23,6 @@ describe('ResetAuthDataService', () => {
mockProvider(LoggerService),
],
});
});
beforeEach(() => {
service = TestBed.inject(ResetAuthDataService);
userService = TestBed.inject(UserService);
flowsDataService = TestBed.inject(FlowsDataService);

View File

@ -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'
);
}
});
});
});

View File

@ -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);

View File

@ -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: <explanation>
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<any>(loggerService, 'logWarning');
const spyLogWarning = vi.spyOn<any, any>(loggerService, 'logWarning');
const config = { configId: 'configId1' };
vi.spyOn<any>(loggerService, 'logDebug').mockImplementation(
vi.spyOn<any, any>(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<any>(checkSessionService, 'pollServerSession');
const spy = vi.spyOn<any, any>(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<any>(checkSessionService, 'pollServerSession');
const spy = vi.spyOn<any, any>(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<any>(checkSessionService, 'clearScheduledHeartBeat');
const spy = vi.spyOn<any, any>(
checkSessionService,
'clearScheduledHeartBeat'
);
checkSessionService.stop();
expect(spy).not.toHaveBeenCalledExactlyOnceWith();
@ -187,11 +191,16 @@ describe('CheckSessionService', () => {
describe('pollServerSession', () => {
beforeEach(() => {
vi.spyOn<any>(checkSessionService, 'init').mockReturnValue(of(undefined));
vi.spyOn<any, any>(checkSessionService, 'init').mockReturnValue(
of(undefined)
);
});
it('increases outstandingMessages', () => {
vi.spyOn<any>(checkSessionService, 'getExistingIframe').mockReturnValue({
vi.spyOn<any, any>(
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<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
null
);
vi.spyOn<any, any>(
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<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
vi.spyOn<any, any>(
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<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
);
vi.spyOn<any, any>(
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<any>(checkSessionService, 'getExistingIframe').mockReturnValue(
{}
vi.spyOn(checkSessionService, 'getExistingIframe').mockReturnValue(
{} as any
);
// biome-ignore lint/suspicious/noEvolvingTypes: <explanation>
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);
});

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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);
},

View File

@ -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: <explanation>
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: <explanation>
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);

View File

@ -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);

View File

@ -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);
});

View File

@ -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 {

View File

@ -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: <explanation>
console.error(message, ...args);
}
logWarning(message: string | object, ...args: any[]): void {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.warn(message, ...args);
}
logDebug(message: string | object, ...args: any[]): void {
// biome-ignore lint/suspicious/noConsole: <explanation>
console.debug(message, ...args);
}
}

View File

@ -1,6 +1,6 @@
export enum LogLevel {
None,
Debug,
Warn,
Error,
None = 0,
Debug = 1,
Warn = 2,
Error = 3,
}

View File

@ -15,9 +15,6 @@ describe('Logger Service', () => {
{ provide: AbstractLoggerService, useClass: ConsoleLoggerService },
],
});
});
beforeEach(() => {
loggerService = TestBed.inject(LoggerService);
});

View File

@ -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: <explanation>
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();
});
});
});

View File

@ -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: '',
});
});
});
});

View File

@ -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<void> {
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(

View File

@ -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();
}
});
});
});

View File

@ -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);

View File

@ -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 () => {

View File

@ -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: <explanation>
// biome-ignore lint/complexity/noVoid: <explanation>
(_: 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: <explanation>
(_: 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();
});

View File

@ -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 });

View File

@ -18,9 +18,6 @@ describe('ResponseTypeValidationService', () => {
mockProvider(FlowHelper),
],
});
});
beforeEach(() => {
responseTypeValidationService = TestBed.inject(
ResponseTypeValidationService
);

View File

@ -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';

View File

@ -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',

View File

@ -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<void> {
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$;
}
}

View File

@ -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();
}
});
});

View File

@ -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);

View File

@ -1,4 +1,3 @@
// biome-ignore lint/nursery/noEnum: <explanation>
export enum EventTypes {
/**
* This only works in the AppModule Constructor

View File

@ -1,4 +1,4 @@
import { EventTypes } from './event-types';
import type { EventTypes } from './event-types';
export interface OidcClientNotification<T> {
type: EventTypes;

View File

@ -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);
});

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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: <explanation>
(Storage as any) = undefined;
expect((service as any).hasStorage()).toBeFalsy();
// biome-ignore lint/correctness/noSelfAssign: <explanation>
// biome-ignore lint/suspicious/noGlobalAssign: <explanation>
Storage = Storage;
});
});

View File

@ -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;

View File

@ -9,9 +9,6 @@ describe('DefaultLocalStorageService', () => {
TestBed.configureTestingModule({
providers: [DefaultLocalStorageService],
});
});
beforeEach(() => {
service = TestBed.inject(DefaultLocalStorageService);
});

View File

@ -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();
}
}

View File

@ -9,9 +9,6 @@ describe('DefaultSessionStorageService', () => {
TestBed.configureTestingModule({
providers: [DefaultSessionStorageService],
});
});
beforeEach(() => {
service = TestBed.inject(DefaultSessionStorageService);
});

View File

@ -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();
}
}

View File

@ -12,9 +12,6 @@ describe('Storage Persistence Service', () => {
TestBed.configureTestingModule({
providers: [mockProvider(BrowserStorageService)],
});
});
beforeEach(() => {
service = TestBed.inject(StoragePersistenceService);
securityStorage = TestBed.inject(BrowserStorageService);
});

View File

@ -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<any> => {
const fetchData: jasmine.Spy = jasmine.createSpy('fetchData');
const fetchData = vi.fn()('fetchData');
fetchData.mockReturnValues(...resp$);

View File

@ -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: <explanation>
const userDataFromSts = null;
const config = {

View File

@ -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);
});

View File

@ -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);
});

View File

@ -99,7 +99,7 @@ export class EqualityService {
return typeof value === 'object';
}
private arraysStrictEqual(arr1: Array<string>, arr2: Array<string>): 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<string>,
arr2: Array<string>
): boolean {
private arraysHaveEqualContent(arr1: string[], arr2: string[]): boolean {
if (arr1.length !== arr2.length) {
return false;
}

View File

@ -9,9 +9,6 @@ describe('Flow Helper Service', () => {
TestBed.configureTestingModule({
providers: [FlowHelper],
});
});
beforeEach(() => {
flowHelper = TestBed.inject(FlowHelper);
});

Some files were not shown because too many files have changed in this diff Show More