Files
oidc-client-rx/src/utils/url/url.service.spec.ts
2025-01-31 02:02:07 +08:00

2191 lines
70 KiB
TypeScript

import { TestBed, mockImplementationWhenArgsEqual } from '@/testing';
import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest';
import type { OpenIdConfiguration } from '../../config/openid-configuration';
import { FlowsDataService } from '../../flows/flows-data.service';
import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { mockProvider } from '../../testing/mock';
import { JwtWindowCryptoService } from '../../validation/jwt-window-crypto.service';
import { FlowHelper } from '../flowHelper/flow-helper.service';
import { UrlService } from './url.service';
describe('UrlService Tests', () => {
let service: UrlService;
let flowHelper: FlowHelper;
let flowsDataService: FlowsDataService;
let jwtWindowCryptoService: JwtWindowCryptoService;
let storagePersistenceService: StoragePersistenceService;
let loggerService: LoggerService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
UrlService,
mockProvider(LoggerService),
mockProvider(FlowsDataService),
FlowHelper,
mockProvider(StoragePersistenceService),
mockProvider(JwtWindowCryptoService),
],
});
});
beforeEach(() => {
service = TestBed.inject(UrlService);
loggerService = TestBed.inject(LoggerService);
flowHelper = TestBed.inject(FlowHelper);
flowsDataService = TestBed.inject(FlowsDataService);
jwtWindowCryptoService = TestBed.inject(JwtWindowCryptoService);
storagePersistenceService = TestBed.inject(StoragePersistenceService);
});
it('should create', () => {
expect(service).toBeTruthy();
});
describe('getUrlWithoutQueryParameters', () => {
it('should return a new instance of the passed URL without any query parameters', () => {
const url = new URL('https://any.url');
const params = [
{ key: 'doot', value: 'boop' },
{ key: 'blep', value: 'blep' },
];
params.forEach((p) => {
url.searchParams.set(p.key, p.value);
});
const sut = service.getUrlWithoutQueryParameters(url);
params.forEach((p) => {
expect(sut.searchParams.has(p.key)).toBeFalsy();
});
});
});
describe('queryParametersExist', () => {
const expected = new URLSearchParams();
const params = [
{ key: 'doot', value: 'boop' },
{ key: 'blep', value: 'blep' },
];
params.forEach((p) => {
expected.set(p.key, p.value);
});
const matchingUrls = [
new URL('https://any.url?doot=boop&blep=blep'),
new URL('https://any.url?doot=boop&blep=blep&woop=doot'),
];
const nonMatchingUrls = [
new URL('https://any.url?doot=boop'),
new URL('https://any.url?blep=blep&woop=doot'),
];
matchingUrls.forEach((mu) => {
it(`should return true for ${mu.toString()}`, () => {
expect(
service.queryParametersExist(expected, mu.searchParams)
).toBeTruthy();
});
});
nonMatchingUrls.forEach((nmu) => {
it(`should return false for ${nmu.toString()}`, () => {
expect(
service.queryParametersExist(expected, nmu.searchParams)
).toBeFalsy();
});
});
});
describe('isCallbackFromSts', () => {
it(`should return false if config says to check redirect URI, and it doesn't match`, () => {
const nonMatchingUrls = [
{
url: 'https://the-redirect.url',
config: {
redirectUrl: 'https://the-redirect.url?with=parameter',
checkRedirectUrlWhenCheckingIfIsCallback: true,
},
},
{
url: 'https://the-redirect.url?wrong=parameter',
config: {
redirectUrl: 'https://the-redirect.url?with=parameter',
checkRedirectUrlWhenCheckingIfIsCallback: true,
},
},
{
url: 'https://not-the-redirect.url',
config: {
redirectUrl: 'https://the-redirect.url',
checkRedirectUrlWhenCheckingIfIsCallback: true,
},
},
];
nonMatchingUrls.forEach((nmu) => {
expect(service.isCallbackFromSts(nmu.url, nmu.config)).toBeFalsy();
});
});
const testingValues = [
{ param: 'code', isCallbackFromSts: true },
{ param: 'state', isCallbackFromSts: true },
{ param: 'token', isCallbackFromSts: true },
{ param: 'id_token', isCallbackFromSts: true },
{ param: 'some_param', isCallbackFromSts: false },
];
testingValues.forEach(({ param, isCallbackFromSts }) => {
it(`should return ${isCallbackFromSts} when param is ${param}`, () => {
const result = service.isCallbackFromSts(
`https://any.url/?${param}=anyvalue`
);
expect(result).toBe(isCallbackFromSts);
});
});
});
describe('getUrlParameter', () => {
it('returns empty string when there is no urlToCheck', () => {
const result = service.getUrlParameter('', 'code');
expect(result).toBe('');
});
it('returns empty string when there is no name', () => {
const result = service.getUrlParameter('url', '');
expect(result).toBe('');
});
it('returns empty string when name is not a uri', () => {
const result = service.getUrlParameter('url', 'anything');
expect(result).toBe('');
});
it('parses Url correctly with hash in the end', () => {
const urlToCheck =
'https://www.example.com/signin?code=thisisacode&state=0000.1234.000#';
const code = service.getUrlParameter(urlToCheck, 'code');
const state = service.getUrlParameter(urlToCheck, 'state');
expect(code).toBe('thisisacode');
expect(state).toBe('0000.1234.000');
});
it('parses url with special chars in param and hash in the end', () => {
const urlToCheck =
'https://www.example.com/signin?code=thisisa$-_.+!*(),code&state=0000.1234.000#';
const code = service.getUrlParameter(urlToCheck, 'code');
const state = service.getUrlParameter(urlToCheck, 'state');
expect(code).toBe('thisisa$-_.+!*(),code');
expect(state).toBe('0000.1234.000');
});
it('parses Url correctly with number&delimiter in params', () => {
const urlToCheck =
'https://www.example.com/signin?code=thisisacode&state=0000.1234.000';
const code = service.getUrlParameter(urlToCheck, 'code');
const state = service.getUrlParameter(urlToCheck, 'state');
expect(code).toBe('thisisacode');
expect(state).toBe('0000.1234.000');
});
it('gets correct param if params divided vith slash', () => {
const urlToCheck =
'https://www.example.com/signin?state=0000.1234.000&ui_locales=de&code=thisisacode#lang=de';
const code = service.getUrlParameter(urlToCheck, 'code');
const state = service.getUrlParameter(urlToCheck, 'state');
expect(code).toBe('thisisacode');
expect(state).toBe('0000.1234.000');
});
it('gets correct params when response_mode=fragment', () => {
// Test url taken from an example in the RFC: https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2
const urlToCheck =
'http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600';
const accessToken = service.getUrlParameter(urlToCheck, 'access_token');
const state = service.getUrlParameter(urlToCheck, 'state');
const tokenType = service.getUrlParameter(urlToCheck, 'token_type');
const expiresIn = service.getUrlParameter(urlToCheck, 'expires_in');
expect(accessToken).toBe('2YotnFZFEjr1zCsicMWpAA');
expect(state).toBe('xyz');
expect(tokenType).toBe('example');
expect(expiresIn).toBe('3600');
});
it('gets correct params when square brackets are present', () => {
const urlToCheck =
'http://example.com/cb#state=abc[&code=abc&arr[]=1&some_param=abc]&arr[]=2&arr[]=3';
const state = service.getUrlParameter(urlToCheck, 'state');
const code = service.getUrlParameter(urlToCheck, 'code');
const someParam = service.getUrlParameter(urlToCheck, 'some_param');
const array = service.getUrlParameter(urlToCheck, 'arr[]');
expect(state).toBe('abc[');
expect(code).toBe('abc');
expect(someParam).toBe('abc]');
expect(['1', '2', '3']).toContain(array);
});
});
describe('createAuthorizeUrl', () => {
it('returns empty string when no authoizationendpoint given -> wellKnownEndpoints null', () => {
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
'https://localhost:44386',
'nonce',
'state'
);
expect(value).toEqual('');
});
it('returns empty string when no authoizationendpoint given -> configurationProvider null', () => {
(service as any).configurationProvider = null;
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
'https://localhost:44386',
'nonce',
'state'
);
expect(value).toEqual('');
});
it('returns empty string when clientId is null', () => {
const config = { configId: 'configId1', clientId: '' };
const authorizationEndpoint = 'authorizationEndpoint';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
'https://localhost:44386',
'nonce',
'state',
config
);
expect(value).toEqual('');
});
it('returns empty string when responseType is null', () => {
const config = {
configId: 'configId1',
clientId: 'something',
responseType: undefined,
};
const authorizationEndpoint = 'authorizationEndpoint';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
'https://localhost:44386',
'nonce',
'state',
config
);
expect(value).toEqual('');
});
it('returns empty string when scope is null', () => {
const config = {
configId: 'configId1',
clientId: 'something',
responseType: 'responsetype',
scope: undefined,
};
const authorizationEndpoint = 'authorizationEndpoint';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
'https://localhost:44386',
'nonce',
'state',
config
);
expect(value).toEqual('');
});
it('createAuthorizeUrl with code flow and codeChallenge adds "code_challenge" and "code_challenge_method" param', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'code';
config.scope = 'openid email profile';
config.redirectUrl = 'https://localhost:44386';
config.customParamsAuthRequest = {
testcustom: 'customvalue',
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint: 'http://example' })
);
const value = (service as any).createAuthorizeUrl(
'codeChallenge', // Code Flow
config.redirectUrl,
'nonce',
'state',
config
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=code' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&code_challenge=codeChallenge&code_challenge_method=S256' +
'&testcustom=customvalue';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl with prompt adds prompt value', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.configId = 'configId1';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config,
'myprompt'
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&prompt=myprompt';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl with prompt and custom values adds prompt value and custom values', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.configId = 'configId1';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config,
'myprompt',
{ to: 'add', as: 'well' }
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&to=add&as=well' +
'&prompt=myprompt';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl with hdParam adds hdparam value', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.hdParam = 'myHdParam';
config.configId = 'configId1';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&hd=myHdParam';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl with custom value', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.configId = 'configId1';
config.customParamsAuthRequest = {
testcustom: 'customvalue',
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&testcustom=customvalue';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl with custom values', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.configId = 'configId1';
config.customParamsAuthRequest = {
t4: 'ABC abc 123',
t3: '#',
t2: '-_.!~*()',
t1: ';,/?:@&=+$',
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state&t4=ABC%20abc%20123&t3=%23&t2=-_.!~*()&t1=%3B%2C%2F%3F%3A%40%26%3D%2B%24';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl creates URL with with custom values and dynamic custom values', () => {
const config = {
authority: 'https://localhost:5001',
redirectUrl: 'https://localhost:44386',
clientId:
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
responseType: 'id_token token',
scope: 'openid email profile',
configId: 'configId1',
customParamsAuthRequest: {
t4: 'ABC abc 123',
t3: '#',
t2: '-_.!~*()',
t1: ';,/?:@&=+$',
},
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config,
null,
{ to: 'add', as: 'well' }
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&t4=ABC%20abc%20123&t3=%23&t2=-_.!~*()&t1=%3B%2C%2F%3F%3A%40%26%3D%2B%24' +
'&to=add&as=well';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl creates URL with custom values equals null and dynamic custom values', () => {
const config = {
authority: 'https://localhost:5001',
redirectUrl: 'https://localhost:44386',
clientId:
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
responseType: 'id_token token',
scope: 'openid email profile',
customParamsAuthRequest: undefined,
configId: 'configId1',
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config,
null,
{ to: 'add', as: 'well' }
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&to=add&as=well';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl creates URL with custom values not given and dynamic custom values', () => {
const config = {
authority: 'https://localhost:5001',
redirectUrl: 'https://localhost:44386',
clientId:
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
responseType: 'id_token token',
scope: 'openid email profile',
configId: 'configId1',
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config,
null,
{ to: 'add', as: 'well' }
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&to=add&as=well';
expect(value).toEqual(expectValue);
});
// https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-oidc
it('createAuthorizeUrl with custom URL like active-directory-b2c', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId = 'myid';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint:
'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config
);
const expectValue =
'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_sign_in' +
'&client_id=myid' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state';
expect(value).toEqual(expectValue);
});
it('createAuthorizeUrl default', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.configId = 'configId1';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'http://example',
})
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=id_token%20token' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state';
expect(value).toEqual(expectValue);
});
it('should add the prompt only once even if it is configured AND passed with `none` in silent renew case, taking the passed one', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'code';
config.scope = 'openid email profile';
config.redirectUrl = 'https://localhost:44386';
config.customParamsAuthRequest = {
prompt: 'select_account',
};
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint: 'http://example' })
);
const value = (service as any).createAuthorizeUrl(
'', // Implicit Flow
config.redirectUrl,
'nonce',
'state',
config,
'somePrompt'
);
const expectValue =
'http://example?client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com' +
'&redirect_uri=https%3A%2F%2Flocalhost%3A44386' +
'&response_type=code' +
'&scope=openid%20email%20profile' +
'&nonce=nonce' +
'&state=state' +
'&code_challenge=' +
'&code_challenge_method=S256' +
'&prompt=somePrompt';
expect(value).toEqual(expectValue);
});
});
describe('createRevocationEndpointBodyAccessToken', () => {
it('createRevocationBody access_token default', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized';
const revocationEndpoint = 'http://example?cod=ddd';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
revocationEndpoint,
})
);
const value = service.createRevocationEndpointBodyAccessToken(
'mytoken',
config
);
const expectValue =
'client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com&token=mytoken&token_type_hint=access_token';
expect(value).toEqual(expectValue);
});
it('createRevocationEndpointBodyAccessToken returns null when no clientId is given', () => {
const config = {
authority: 'https://localhost:5001',
clientId: '',
} as OpenIdConfiguration;
const value = service.createRevocationEndpointBodyAccessToken(
'mytoken',
config
);
expect(value).toBeNull();
});
});
describe('createRevocationEndpointBodyRefreshToken', () => {
it('createRevocationBody refresh_token default', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized';
const revocationEndpoint = 'http://example?cod=ddd';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
revocationEndpoint,
})
);
const value = service.createRevocationEndpointBodyRefreshToken(
'mytoken',
config
);
const expectValue =
'client_id=188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com&token=mytoken&token_type_hint=refresh_token';
expect(value).toEqual(expectValue);
});
it('createRevocationEndpointBodyRefreshToken returns null when no clientId is given', () => {
const config = {
authority: 'https://localhost:5001',
clientId: undefined,
} as OpenIdConfiguration;
const value = service.createRevocationEndpointBodyRefreshToken(
'mytoken',
config
);
expect(value).toBeNull();
});
});
describe('getRevocationEndpointUrl', () => {
it('getRevocationEndpointUrl with params', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized';
const revocationEndpoint = 'http://example?cod=ddd';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
revocationEndpoint,
})
);
const value = service.getRevocationEndpointUrl(config);
const expectValue = 'http://example';
expect(value).toEqual(expectValue);
});
it('getRevocationEndpointUrl default', () => {
const config = {
authority: 'https://localhost:5001',
} as OpenIdConfiguration;
config.redirectUrl = 'https://localhost:44386';
config.clientId =
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com';
config.responseType = 'id_token token';
config.scope = 'openid email profile';
config.postLogoutRedirectUri = 'https://localhost:44386/Unauthorized';
const revocationEndpoint = 'http://example';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
revocationEndpoint,
})
);
const value = service.getRevocationEndpointUrl(config);
const expectValue = 'http://example';
expect(value).toEqual(expectValue);
});
it('getRevocationEndpointUrl returns null when there is not revociationendpoint given', () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', {}],
() => ({
revocationEndpoint: null,
})
);
const value = service.getRevocationEndpointUrl({});
expect(value).toBeNull();
});
it('getRevocationEndpointUrl returns null when there is no wellKnownEndpoints given', () => {
const value = service.getRevocationEndpointUrl({});
expect(value).toBeNull();
});
});
describe('getRedirectUrl', () => {
it('returns configured redirectUrl', () => {
const config = { configId: 'configId1', redirectUrl: 'one-url' };
const url = (service as any).getRedirectUrl(config);
expect(url).toEqual('one-url');
});
it('returns redefined redirectUrl in AuthOptions', () => {
const config = { configId: 'configId1', redirectUrl: 'one-url' };
const url = (service as any).getRedirectUrl(config, {
redirectUrl: 'other-url',
});
expect(url).toEqual('other-url');
});
});
describe('getAuthorizeUrl', () => {
it('returns null if no config is given', async () => {
const url = await lastValueFrom(service.getAuthorizeUrl(null));
expect(url).toBeNull();
});
it('returns null if current flow is code flow and no redirect url is defined', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
service.getAuthorizeUrl({ configId: 'configId1' }).subscribe((result) => {
expect(result).toBeNull();
});
});
it('returns empty string if current flow is code flow, config disabled pkce and there is a redirecturl', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const config = {
configId: 'configId1',
disablePkce: true,
redirectUrl: 'some-redirectUrl',
} as OpenIdConfiguration;
service.getAuthorizeUrl(config).subscribe((result) => {
expect(result).toBe('');
});
});
it('returns url if current flow is code flow, config disabled pkce, there is a redirecturl and awkep are given', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const config = {
configId: 'configId1',
disablePkce: false,
redirectUrl: 'some-redirectUrl',
clientId: 'some-clientId',
responseType: 'testResponseType',
scope: 'testScope',
hdParam: undefined,
customParamsAuthRequest: undefined,
} as OpenIdConfiguration;
const authorizationEndpoint = 'authorizationEndpoint';
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of('some-code-challenge')
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
service.getAuthorizeUrl(config).subscribe((result) => {
expect(result).toBe(
'authorizationEndpoint?client_id=some-clientId&redirect_uri=some-redirectUrl&response_type=testResponseType&scope=testScope&nonce=undefined&state=undefined&code_challenge=some-code-challenge&code_challenge_method=S256'
);
});
});
it('calls createUrlImplicitFlowAuthorize if current flow is NOT code flow', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
const spyCreateUrlCodeFlowAuthorize = vi.spyOn(
service as any,
'createUrlCodeFlowAuthorize'
);
const spyCreateUrlImplicitFlowAuthorize = vi.spyOn(
service as any,
'createUrlImplicitFlowAuthorize'
);
service.getAuthorizeUrl({ configId: 'configId1' }).subscribe(() => {
expect(spyCreateUrlCodeFlowAuthorize).not.toHaveBeenCalled();
expect(spyCreateUrlImplicitFlowAuthorize).toHaveBeenCalled();
});
});
it('return empty string if flow is not code flow and createUrlImplicitFlowAuthorize returns falsy', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
const spy = vi
.spyOn(service as any, 'createUrlImplicitFlowAuthorize')
.mockReturnValue('');
const resultObs$ = service.getAuthorizeUrl({ configId: 'configId1' });
resultObs$.subscribe((result) => {
expect(spy).toHaveBeenCalled();
expect(result).toBe('');
});
});
});
describe('getRefreshSessionSilentRenewUrl', () => {
it('calls createUrlCodeFlowWithSilentRenew if current flow is code flow', () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(true);
const spy = vi.spyOn(service as any, 'createUrlCodeFlowWithSilentRenew');
service.getRefreshSessionSilentRenewUrl({ configId: 'configId1' });
expect(spy).toHaveBeenCalled();
});
it('calls createUrlImplicitFlowWithSilentRenew if current flow is NOT code flow', () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
const spyCreateUrlCodeFlowWithSilentRenew = vi.spyOn(
service as any,
'createUrlCodeFlowWithSilentRenew'
);
const spyCreateUrlImplicitFlowWithSilentRenew = vi.spyOn(
service as any,
'createUrlImplicitFlowWithSilentRenew'
);
service.getRefreshSessionSilentRenewUrl({ configId: 'configId1' });
expect(spyCreateUrlCodeFlowWithSilentRenew).not.toHaveBeenCalled();
expect(spyCreateUrlImplicitFlowWithSilentRenew).toHaveBeenCalled();
});
it('return empty string if flow is not code flow and createUrlImplicitFlowWithSilentRenew returns falsy', async () => {
vi.spyOn(flowHelper, 'isCurrentFlowCodeFlow').mockReturnValue(false);
const spy = vi
.spyOn(service as any, 'createUrlImplicitFlowWithSilentRenew')
.mockReturnValue('');
const resultObs$ = service.getRefreshSessionSilentRenewUrl({
configId: 'configId1',
});
resultObs$.subscribe((result) => {
expect(spy).toHaveBeenCalled();
expect(result).toBe('');
});
});
});
describe('createBodyForCodeFlowCodeRequest', () => {
it('returns null if no code verifier is set', () => {
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(null);
const result = service.createBodyForCodeFlowCodeRequest(
'notRelevantParam',
{ configId: 'configId1' }
);
expect(result).toBeNull();
});
it('returns null if no clientId is set', () => {
const codeVerifier = 'codeverifier';
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(
codeVerifier
);
const clientId = '';
const result = service.createBodyForCodeFlowCodeRequest(
'notRelevantParam',
{ clientId }
);
expect(result).toBeNull();
});
it('returns null if silentrenewRunning is false and redirectUrl is falsy', () => {
const codeVerifier = 'codeverifier';
const code = 'code';
const redirectUrl = '';
const clientId = 'clientId';
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
const result = service.createBodyForCodeFlowCodeRequest(code, {
clientId,
redirectUrl,
});
expect(result).toBeNull();
});
it('returns correctUrl with silentrenewRunning is false', () => {
const codeVerifier = 'codeverifier';
const code = 'code';
const redirectUrl = 'redirectUrl';
const clientId = 'clientId';
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(false);
const result = service.createBodyForCodeFlowCodeRequest(code, {
clientId,
redirectUrl,
});
const expected = `grant_type=authorization_code&client_id=${clientId}&code_verifier=${codeVerifier}&code=${code}&redirect_uri=${redirectUrl}`;
expect(result).toBe(expected);
});
it('returns correctUrl with silentrenewRunning is true', () => {
const codeVerifier = 'codeverifier';
const code = 'code';
const silentRenewUrl = 'silentRenewUrl';
const clientId = 'clientId';
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const result = service.createBodyForCodeFlowCodeRequest(code, {
clientId,
silentRenewUrl,
});
const expected = `grant_type=authorization_code&client_id=${clientId}&code_verifier=${codeVerifier}&code=${code}&redirect_uri=${silentRenewUrl}`;
expect(result).toBe(expected);
});
it('returns correctUrl when customTokenParams are provided', () => {
const codeVerifier = 'codeverifier';
const code = 'code';
const silentRenewUrl = 'silentRenewUrl';
const clientId = 'clientId';
const customTokenParams = { foo: 'bar' };
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(flowsDataService, 'isSilentRenewRunning').mockReturnValue(true);
const result = service.createBodyForCodeFlowCodeRequest(
code,
{ clientId, silentRenewUrl },
customTokenParams
);
const expected = `grant_type=authorization_code&client_id=${clientId}&code_verifier=${codeVerifier}&code=${code}&foo=bar&redirect_uri=${silentRenewUrl}`;
expect(result).toBe(expected);
});
it('returns null if pkce is disabled and no code verifier is given', () => {
const code = 'code';
const customTokenParams = { foo: 'bar' };
const config = {
clientId: 'clientId',
disablePkce: false,
};
vi.spyOn(flowsDataService, 'getCodeVerifier').mockReturnValue(null);
const loggerspy = vi.spyOn(loggerService, 'logError');
const result = service.createBodyForCodeFlowCodeRequest(
code,
config,
customTokenParams
);
expect(result).toBe(null);
expect(loggerspy).toHaveBeenCalledExactlyOnceWith(
config,
'CodeVerifier is not set ',
null
);
});
});
describe('createBodyForCodeFlowRefreshTokensRequest', () => {
it('returns correct URL', () => {
const clientId = 'clientId';
const refreshToken = 'refreshToken';
const result = service.createBodyForCodeFlowRefreshTokensRequest(
refreshToken,
{ clientId }
);
expect(result).toBe(
`grant_type=refresh_token&client_id=${clientId}&refresh_token=${refreshToken}`
);
});
it('returns correct URL with custom params if custom params are passed', () => {
const clientId = 'clientId';
const refreshToken = 'refreshToken';
const result = service.createBodyForCodeFlowRefreshTokensRequest(
refreshToken,
{ clientId },
{ any: 'thing' }
);
expect(result).toBe(
`grant_type=refresh_token&client_id=${clientId}&refresh_token=${refreshToken}&any=thing`
);
});
it('returns null if clientId is falsy', () => {
const clientId = '';
const refreshToken = 'refreshToken';
const result = service.createBodyForCodeFlowRefreshTokensRequest(
refreshToken,
{ clientId }
);
expect(result).toBe(null);
});
});
describe('createBodyForParCodeFlowRequest', () => {
it('returns null redirectUrl is falsy', async () => {
const resultObs$ = service.createBodyForParCodeFlowRequest({
redirectUrl: '',
});
resultObs$.subscribe((result) => {
expect(result).toBe(null);
});
});
it('returns basic URL with no extras if properties are given', async () => {
const config = {
clientId: 'testClientId',
responseType: 'testResponseType',
scope: 'testScope',
hdParam: undefined,
customParamsAuthRequest: undefined,
redirectUrl: 'testRedirectUrl',
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue('testState');
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce');
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
'testCodeVerifier'
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of('testCodeChallenge')
);
const resultObs$ = service.createBodyForParCodeFlowRequest(config);
resultObs$.subscribe((result) => {
expect(result).toBe(
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256'
);
});
});
it('returns basic URL with hdParam if properties are given', async () => {
const config = {
clientId: 'testClientId',
responseType: 'testResponseType',
scope: 'testScope',
hdParam: 'testHdParam',
customParamsAuthRequest: undefined,
redirectUrl: 'testRedirectUrl',
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue('testState');
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce');
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
'testCodeVerifier'
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of('testCodeChallenge')
);
const resultObs$ = service.createBodyForParCodeFlowRequest(config);
resultObs$.subscribe((result) => {
expect(result).toBe(
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam'
);
});
});
it('returns basic URL with hdParam and custom params if properties are given', async () => {
const config = {
clientId: 'testClientId',
responseType: 'testResponseType',
scope: 'testScope',
hdParam: 'testHdParam',
customParamsAuthRequest: { any: 'thing' },
redirectUrl: 'testRedirectUrl',
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue('testState');
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce');
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
'testCodeVerifier'
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of('testCodeChallenge')
);
const resultObs$ = service.createBodyForParCodeFlowRequest(config);
resultObs$.subscribe((result) => {
expect(result).toBe(
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing'
);
});
});
it('returns basic URL with hdParam and custom params and passed cutom params if properties are given', async () => {
const config = {
clientId: 'testClientId',
responseType: 'testResponseType',
scope: 'testScope',
hdParam: 'testHdParam',
customParamsAuthRequest: { any: 'thing' },
redirectUrl: 'testRedirectUrl',
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue('testState');
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue('testNonce');
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
'testCodeVerifier'
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of('testCodeChallenge')
);
const resultObs$ = service.createBodyForParCodeFlowRequest(config, {
customParams: {
any: 'otherThing',
},
});
resultObs$.subscribe((result) => {
expect(result).toBe(
'client_id=testClientId&redirect_uri=testRedirectUrl&response_type=testResponseType&scope=testScope&nonce=testNonce&state=testState&code_challenge=testCodeChallenge&code_challenge_method=S256&hd=testHdParam&any=thing&any=otherThing'
);
});
});
});
describe('createUrlImplicitFlowWithSilentRenew', () => {
it('returns null if silentrenewUrl is falsy', () => {
const state = 'testState';
const nonce = 'testNonce';
const silentRenewUrl = null;
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
const config = {
silentRenewUrl,
};
const serviceAsAny = service as any;
const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config);
expect(result).toBeNull();
});
it('returns correct URL if wellknownendpoints are given', () => {
const state = 'testState';
const nonce = 'testNonce';
const silentRenewUrl = 'http://any-url.com';
const authorizationEndpoint = 'authorizationEndpoint';
const clientId = 'clientId';
const responseType = 'responseType';
const scope = 'testScope';
const config = {
silentRenewUrl,
clientId,
responseType,
scope,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint,
})
);
const serviceAsAny = service as any;
const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config);
expect(result).toBe(
`authorizationEndpoint?client_id=${clientId}&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&prompt=none`
);
});
it('returns correct url if wellknownendpoints are not given', () => {
const state = 'testState';
const nonce = 'testNonce';
const silentRenewUrl = 'http://any-url.com';
const clientId = 'clientId';
const responseType = 'responseType';
const config = {
silentRenewUrl,
clientId,
responseType,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => null
);
const serviceAsAny = service as any;
const result = serviceAsAny.createUrlImplicitFlowWithSilentRenew(config);
expect(result).toBe(null);
});
});
describe('createUrlCodeFlowWithSilentRenew', () => {
it('returns empty string if silentrenewUrl is falsy', async () => {
const state = 'testState';
const nonce = 'testNonce';
const silentRenewUrl = null;
const codeVerifier = 'codeVerifier';
const codeChallenge = 'codeChallenge ';
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of(codeChallenge)
);
const config = {
silentRenewUrl,
};
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config);
resultObs$.subscribe((result: any) => {
expect(result).toBe('');
});
});
it('returns correct URL if wellknownendpoints are given', async () => {
const state = 'testState';
const nonce = 'testNonce';
const silentRenewUrl = 'http://any-url.com';
const authorizationEndpoint = 'authorizationEndpoint';
const clientId = 'clientId';
const responseType = 'responseType';
const codeVerifier = 'codeVerifier';
const codeChallenge = 'codeChallenge ';
const scope = 'testScope';
const config = {
silentRenewUrl,
clientId,
responseType,
scope,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of(codeChallenge)
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config);
resultObs$.subscribe((result: any) => {
expect(result).toBe(
`authorizationEndpoint?client_id=${clientId}&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&prompt=none`
);
});
});
it('returns empty string if no wellknownendpoints are given', async () => {
const state = 'testState';
const nonce = 'testNonce';
const silentRenewUrl = 'http://any-url.com';
const clientId = 'clientId';
const responseType = 'responseType';
const codeVerifier = 'codeVerifier';
const codeChallenge = 'codeChallenge ';
const config = {
silentRenewUrl,
clientId,
responseType,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of(codeChallenge)
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => null
);
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowWithSilentRenew(config);
resultObs$.subscribe((result: any) => {
expect(result).toBe('');
});
});
});
describe('createUrlImplicitFlowAuthorize', () => {
it('returns correct URL if wellknownendpoints are given', () => {
const state = 'testState';
const nonce = 'testNonce';
const redirectUrl = 'http://any-url.com';
const authorizationEndpoint = 'authorizationEndpoint';
const clientId = 'clientId';
const responseType = 'responseType';
const scope = 'testScope';
const config = {
redirectUrl,
clientId,
responseType,
scope,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const serviceAsAny = service as any;
const result = serviceAsAny.createUrlImplicitFlowAuthorize(config);
expect(result).toBe(
`authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}`
);
});
it('returns empty string if no wellknownendpoints are given', () => {
const state = 'testState';
const nonce = 'testNonce';
const redirectUrl = 'http://any-url.com';
const clientId = 'clientId';
const responseType = 'responseType';
const config = { redirectUrl, clientId, responseType };
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => null
);
const serviceAsAny = service as any;
const result = serviceAsAny.createUrlImplicitFlowAuthorize(config);
expect(result).toBe(null);
});
it('returns null if there is nor redirecturl', () => {
const state = 'testState';
const nonce = 'testNonce';
const redirectUrl = '';
const clientId = 'clientId';
const responseType = 'responseType';
const config = { redirectUrl, clientId, responseType };
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => null
);
const serviceAsAny = service as any;
const result = serviceAsAny.createUrlImplicitFlowAuthorize(config);
expect(result).toBe(null);
});
});
describe('createUrlCodeFlowAuthorize', () => {
it('returns null if redirectUrl is falsy', async () => {
const state = 'testState';
const nonce = 'testNonce';
const redirectUrl = null;
const config = {
redirectUrl,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config);
resultObs$.subscribe((result: any) => {
expect(result).toBeNull();
});
});
it('returns correct URL if wellknownendpoints are given', async () => {
const state = 'testState';
const nonce = 'testNonce';
const scope = 'testScope';
const redirectUrl = 'http://any-url.com';
const authorizationEndpoint = 'authorizationEndpoint';
const clientId = 'clientId';
const responseType = 'responseType';
const codeVerifier = 'codeVerifier';
const codeChallenge = 'codeChallenge ';
const config = {
redirectUrl,
clientId,
responseType,
scope,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of(codeChallenge)
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config);
resultObs$.subscribe((result: any) => {
expect(result).toBe(
`authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}`
);
});
});
it('returns correct URL if wellknownendpoints and custom params are given', async () => {
const state = 'testState';
const nonce = 'testNonce';
const scope = 'testScope';
const redirectUrl = 'http://any-url.com';
const authorizationEndpoint = 'authorizationEndpoint';
const clientId = 'clientId';
const responseType = 'responseType';
const codeVerifier = 'codeVerifier';
const codeChallenge = 'codeChallenge';
const configId = 'configId1';
const config = {
redirectUrl,
clientId,
responseType,
scope,
configId,
};
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of(codeChallenge)
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({ authorizationEndpoint })
);
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config, {
customParams: { to: 'add', as: 'well' },
});
resultObs$.subscribe((result: any) => {
expect(result).toBe(
`authorizationEndpoint?client_id=clientId&redirect_uri=http%3A%2F%2Fany-url.com&response_type=${responseType}&scope=${scope}&nonce=${nonce}&state=${state}&to=add&as=well`
);
});
});
it('returns empty string if no wellknownendpoints are given', async () => {
const state = 'testState';
const nonce = 'testNonce';
const redirectUrl = 'http://any-url.com';
const clientId = 'clientId';
const responseType = 'responseType';
const codeVerifier = 'codeVerifier';
const codeChallenge = 'codeChallenge ';
const config = { redirectUrl, clientId, responseType };
vi.spyOn(
flowsDataService,
'getExistingOrCreateAuthStateControl'
).mockReturnValue(state);
vi.spyOn(flowsDataService, 'createNonce').mockReturnValue(nonce);
vi.spyOn(flowsDataService, 'createCodeVerifier').mockReturnValue(
codeVerifier
);
vi.spyOn(jwtWindowCryptoService, 'generateCodeChallenge').mockReturnValue(
of(codeChallenge)
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => null
);
const serviceAsAny = service as any;
const resultObs$ = serviceAsAny.createUrlCodeFlowAuthorize(config);
resultObs$.subscribe((result: any) => {
expect(result).toBe('');
});
});
});
describe('getEndSessionUrl', () => {
it('returns null if no config given', () => {
const value = service.getEndSessionUrl(null);
expect(value).toBeNull();
});
it('create URL when all parameters given', () => {
//Arrange
const config = {
postLogoutRedirectUri: 'https://localhost:44386/Unauthorized',
} as OpenIdConfiguration;
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'mytoken'
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
endSessionEndpoint: 'http://example',
})
);
// Act
const value = service.getEndSessionUrl(config);
// Assert
const expectValue =
'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized';
expect(value).toEqual(expectValue);
});
it('create URL when all parameters given but no idTokenHint', () => {
// Arrange
const config = {
postLogoutRedirectUri: 'https://localhost:44386/Unauthorized',
} as OpenIdConfiguration;
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue('');
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
endSessionEndpoint: 'http://example',
})
);
// Act
const value = service.getEndSessionUrl(config);
// Assert
const expectValue =
'http://example?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized';
expect(value).toEqual(expectValue);
});
it('create URL when all parameters and customParamsEndSession given', () => {
// Arrange
const config = {
postLogoutRedirectUri: 'https://localhost:44386/Unauthorized',
} as OpenIdConfiguration;
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'mytoken'
);
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
endSessionEndpoint: 'http://example',
})
);
// Act
const value = service.getEndSessionUrl(config, { param: 'to-add' });
// Assert
const expectValue =
'http://example?id_token_hint=mytoken&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized&param=to-add';
expect(value).toEqual(expectValue);
});
it('with azure-ad-b2c policy parameter', () => {
// Arrange
const config = {
postLogoutRedirectUri: 'https://localhost:44386/Unauthorized',
} as OpenIdConfiguration;
const endSessionEndpoint =
'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in';
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
endSessionEndpoint,
})
);
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'UzI1NiIsImtpZCI6Il'
);
// Act
const value = service.getEndSessionUrl(config);
// Assert
const expectValue =
'https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/oauth2/v2.0/logout?p=b2c_1_sign_in' +
'&id_token_hint=UzI1NiIsImtpZCI6Il' +
'&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44386%2FUnauthorized';
expect(value).toEqual(expectValue);
});
it('create URL without postLogoutRedirectUri when not given', () => {
const config = {
postLogoutRedirectUri: '',
} as OpenIdConfiguration;
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
endSessionEndpoint: 'http://example',
})
);
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'mytoken'
);
// Act
const value = service.getEndSessionUrl(config);
// Assert
const expectValue = 'http://example?id_token_hint=mytoken';
expect(value).toEqual(expectValue);
});
it('returns null if no wellknownEndpoints.endSessionEndpoint given', () => {
// Arrange
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', {}],
() => ({
endSessionEndpoint: null,
})
);
vi.spyOn(storagePersistenceService, 'getIdToken').mockReturnValue(
'mytoken'
);
// Act
const value = service.getEndSessionUrl({});
// Assert
expect(value).toEqual(null);
});
it('returns auth0 format URL if authority ends with .auth0', () => {
// Arrange
const config = {
authority: 'something.auth0.com',
clientId: 'someClientId',
postLogoutRedirectUri: 'https://localhost:1234/unauthorized',
};
// Act
const value = service.getEndSessionUrl(config);
// Assert
const expectValue =
'something.auth0.com/v2/logout?client_id=someClientId&returnTo=https://localhost:1234/unauthorized';
expect(value).toEqual(expectValue);
});
});
describe('getAuthorizeParUrl', () => {
it('returns null if authWellKnownEndPoints is undefined', () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => null
);
const result = service.getAuthorizeParUrl('', { configId: 'configId1' });
expect(result).toBe(null);
});
it('returns null if authWellKnownEndPoints-authorizationEndpoint is undefined', () => {
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', { configId: 'configId1' }],
() => ({
notAuthorizationEndpoint: 'anything',
})
);
const result = service.getAuthorizeParUrl('', { configId: 'configId1' });
expect(result).toBe(null);
});
it('returns null if configurationProvider.openIDConfiguration has no clientId', () => {
const config = { clientId: '' } as OpenIdConfiguration;
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'anything',
})
);
const result = service.getAuthorizeParUrl('', config);
expect(result).toBe(null);
});
it('returns correct URL when everything is given', () => {
const config = { clientId: 'clientId' };
mockImplementationWhenArgsEqual(
vi.spyOn(storagePersistenceService, 'read'),
['authWellKnownEndPoints', config],
() => ({
authorizationEndpoint: 'anything',
})
);
const result = service.getAuthorizeParUrl('passedRequestUri', config);
expect(result).toBe(
'anything?request_uri=passedRequestUri&client_id=clientId'
);
});
});
});