Files
oidc-client-rx/src/validation/token-validation.service.spec.ts
2025-01-30 20:02:28 +08:00

863 lines
32 KiB
TypeScript

import { TestBed } from '@/testing';
import { lastValueFrom, of } from 'rxjs';
import { vi } from 'vitest';
import { JwkExtractor } from '../extractors/jwk.extractor';
import { LoggerService } from '../logging/logger.service';
import { mockProvider } from '../testing/mock';
import { CryptoService } from '../utils/crypto/crypto.service';
import { TokenHelperService } from '../utils/tokenHelper/token-helper.service';
import { JwkWindowCryptoService } from './jwk-window-crypto.service';
import { JwtWindowCryptoService } from './jwt-window-crypto.service';
import { TokenValidationService } from './token-validation.service';
describe('TokenValidationService', () => {
let tokenValidationService: TokenValidationService;
let tokenHelperService: TokenHelperService;
let jwtWindowCryptoService: JwtWindowCryptoService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
TokenValidationService,
mockProvider(LoggerService),
mockProvider(TokenHelperService),
JwkExtractor,
JwkWindowCryptoService,
JwtWindowCryptoService,
CryptoService,
],
});
});
beforeEach(() => {
tokenValidationService = TestBed.inject(TokenValidationService);
tokenHelperService = TestBed.inject(TokenHelperService);
jwtWindowCryptoService = TestBed.inject(JwtWindowCryptoService);
});
it('should create', () => {
expect(tokenValidationService).toBeTruthy();
});
describe('validateIdTokenAud', () => {
it('returns true if aud is string and passed aud matches idToken.aud', () => {
const dataIdToken = { aud: 'banana' };
const valueTrue = tokenValidationService.validateIdTokenAud(
dataIdToken,
'banana',
{ configId: 'configId1' }
);
expect(valueTrue).toEqual(true);
});
it('returns false if aud is string and passed aud does not match idToken.aud', () => {
const dataIdToken = { aud: 'banana' };
const valueFalse = tokenValidationService.validateIdTokenAud(
dataIdToken,
'bananammmm',
{ configId: 'configId1' }
);
expect(valueFalse).toEqual(false);
});
it('returns true if aud is string array and passed aud is included in the array', () => {
const dataIdToken = {
aud: ['banana', 'apple', 'https://nice.dom'],
};
const audValidTrue = tokenValidationService.validateIdTokenAud(
dataIdToken,
'apple',
{ configId: 'configId1' }
);
expect(audValidTrue).toEqual(true);
});
it('returns false if aud is string array and passed aud is NOT included in the array', () => {
const dataIdToken = {
aud: ['banana', 'apple', 'https://nice.dom'],
};
const audValidFalse = tokenValidationService.validateIdTokenAud(
dataIdToken,
'https://nice.domunnnnnnkoem',
{
configId: 'configId1',
}
);
expect(audValidFalse).toEqual(false);
});
});
describe('validateIdTokenNonce', () => {
it('should validate id token nonce after code grant when match', () => {
expect(
tokenValidationService.validateIdTokenNonce(
{ nonce: 'test1' },
'test1',
false,
{ configId: 'configId1' }
)
).toBe(true);
});
it('should not validate id token nonce after code grant when no match', () => {
expect(
tokenValidationService.validateIdTokenNonce(
{ nonce: 'test1' },
'test2',
false,
{ configId: 'configId1' }
)
).toBe(false);
});
it('should validate id token nonce after refresh token grant when undefined and no ignore', () => {
expect(
tokenValidationService.validateIdTokenNonce(
{ nonce: undefined },
TokenValidationService.refreshTokenNoncePlaceholder,
false,
{
configId: 'configId1',
}
)
).toBe(true);
});
it('should validate id token nonce after refresh token grant when undefined and ignore', () => {
expect(
tokenValidationService.validateIdTokenNonce(
{ nonce: undefined },
TokenValidationService.refreshTokenNoncePlaceholder,
true,
{
configId: 'configId1',
}
)
).toBe(true);
});
it('should validate id token nonce after refresh token grant when defined and ignore', () => {
expect(
tokenValidationService.validateIdTokenNonce(
{ nonce: 'test1' },
TokenValidationService.refreshTokenNoncePlaceholder,
true,
{
configId: 'configId1',
}
)
).toBe(true);
});
it('should not validate id token nonce after refresh token grant when defined and no ignore', () => {
expect(
tokenValidationService.validateIdTokenNonce(
{ nonce: 'test1' },
TokenValidationService.refreshTokenNoncePlaceholder,
false,
{
configId: 'configId1',
}
)
).toBe(false);
});
});
describe('validateIdTokenAzpExistsIfMoreThanOneAud', () => {
it('returns false if aud is array, öength is bigger than 1 and has no azp property', () => {
const dataIdToken = {
aud: ['one', 'two'],
};
const result =
tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
dataIdToken
);
expect(result).toBe(false);
});
it('returns false if aud is array, ength is bigger than 1 and has no azp property', () => {
const result =
tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(null);
expect(result).toBe(false);
});
});
describe('validateIdTokenAzpValid', () => {
it('returns true dataIdToken param is null', () => {
const result = tokenValidationService.validateIdTokenAzpValid(null, '');
expect(result).toBe(true);
});
it('returns false when aud is an array and client id is NOT in the aud array', () => {
const dataIdToken = {
aud: [
'banana',
'apple',
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
],
azp: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
};
const azpInvalid = tokenValidationService.validateIdTokenAzpValid(
dataIdToken,
'bananammmm'
);
expect(azpInvalid).toEqual(false);
});
it('returns true when aud is an array and client id is in the aud array', () => {
const dataIdToken = {
aud: [
'banana',
'apple',
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
],
azp: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
};
const azpValid = tokenValidationService.validateIdTokenAzpValid(
dataIdToken,
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com'
);
expect(azpValid).toEqual(true);
});
it('returns true if ID token has no azp property', () => {
const dataIdToken = {
noAzpProperty: 'something',
};
const azpValid = tokenValidationService.validateIdTokenAzpValid(
dataIdToken,
'bananammmm'
);
expect(azpValid).toEqual(true);
});
});
describe('validateIdTokenAzpExistsIfMoreThanOneAud', () => {
it('returns true if aud is array and aud contains azp', () => {
const dataIdToken = {
aud: [
'banana',
'apple',
'188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
],
azp: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com',
};
const valueTrue =
tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
dataIdToken
);
expect(valueTrue).toEqual(true);
});
it('returns true if aud is array but only has one item', () => {
const dataIdToken = {
aud: ['banana'],
};
const valueTrue =
tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
dataIdToken
);
expect(valueTrue).toEqual(true);
});
it('returns true if aud is NOT an array', () => {
const dataIdToken = {
aud: 'banana',
};
const valueTrue =
tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(
dataIdToken
);
expect(valueTrue).toEqual(true);
});
});
describe('validateRequiredIdToken', () => {
it('returns false if property iat is missing', () => {
const decodedIdToken = {
iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
aud: 'bad',
exp: 1589210086,
// iat: 1589206486,
};
const result = tokenValidationService.validateRequiredIdToken(
decodedIdToken,
{ configId: 'configId1' }
);
expect(result).toEqual(false);
});
it('returns false if property exp is missing', () => {
const decodedIdToken = {
iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
aud: 'bad',
// exp: 1589210086,
iat: 1589206486,
};
const result = tokenValidationService.validateRequiredIdToken(
decodedIdToken,
{ configId: 'configId1' }
);
expect(result).toEqual(false);
});
it('returns false if property aud is missing', () => {
const decodedIdToken = {
iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
// aud: 'bad',
exp: 1589210086,
iat: 1589206486,
};
const result = tokenValidationService.validateRequiredIdToken(
decodedIdToken,
{ configId: 'configId1' }
);
expect(result).toEqual(false);
});
it('returns false if property sub is missing', () => {
const decodedIdToken = {
iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
// sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
aud: 'bad',
exp: 1589210086,
iat: 1589206486,
};
const result = tokenValidationService.validateRequiredIdToken(
decodedIdToken,
{ configId: 'configId1' }
);
expect(result).toEqual(false);
});
it('returns false if property iss is missing', () => {
const decodedIdToken = {
// iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
aud: 'bad',
exp: 1589210086,
iat: 1589206486,
};
const result = tokenValidationService.validateRequiredIdToken(
decodedIdToken,
{ configId: 'configId1' }
);
expect(result).toEqual(false);
});
it('returns true if all is valid', () => {
const decodedIdToken = {
iss: 'https://damienbod.b2clogin.com/a0958f45-195b-4036-9259-de2f7e594db6/v2.0/',
sub: 'f836f380-3c64-4802-8dbc-011981c068f5',
aud: 'bad',
exp: 1589210086,
iat: 1589206486,
};
const result = tokenValidationService.validateRequiredIdToken(
decodedIdToken,
{ configId: 'configId1' }
);
expect(result).toEqual(true);
});
});
describe('validateIdTokenIss', () => {
it('returns true if issuer matches iss in idToken', () => {
const decodedIdToken = {
iss: 'xc',
};
const valueTrue = tokenValidationService.validateIdTokenIss(
decodedIdToken,
'xc',
{ configId: 'configId1' }
);
expect(valueTrue).toEqual(true);
});
it('returns false if issuer does not match iss in idToken', () => {
const decodedIdToken = {
iss: 'xc',
};
const valueFalse = tokenValidationService.validateIdTokenIss(
decodedIdToken,
'xcjjjj',
{ configId: 'configId1' }
);
expect(valueFalse).toEqual(false);
});
});
describe('validateIdTokenIatMaxOffset', () => {
it('returns true if validationIsDisabled', () => {
const result = tokenValidationService.validateIdTokenIatMaxOffset(
null,
0,
true,
{ configId: 'configId1' }
);
expect(result).toBe(true);
});
it('returns false if dataIdToken has no property "iat"', () => {
const dataIdToken = {
notIat: 'test',
};
const result = tokenValidationService.validateIdTokenIatMaxOffset(
dataIdToken,
0,
false,
{ configId: 'configId1' }
);
expect(result).toBe(false);
});
it('returns true if time is Mon Jan 19 1970 10:26:46 GMT+0100, and the offset is big like 500000000000 seconds', () => {
const decodedIdToken = {
iat: 1589206486, // Mon Jan 19 1970 10:26:46 GMT+0100 (Central European Standard Time)
};
const valueTrue = tokenValidationService.validateIdTokenIatMaxOffset(
decodedIdToken,
500000000000,
false,
{ configId: 'configId1' }
);
expect(valueTrue).toEqual(true);
});
it('returns false if time is Sat Nov 09 1985 02:47:57 GMT+0100, and the offset is 0 seconds', () => {
const decodedIdTokenNegIat = {
iat: 500348877430, // Sat Nov 09 1985 02:47:57 GMT+0100 (Central European Standard Time)
};
const valueFalseNeg = tokenValidationService.validateIdTokenIatMaxOffset(
decodedIdTokenNegIat,
0,
false,
{ configId: 'configId1' }
);
expect(valueFalseNeg).toEqual(false);
});
it('returns true if time is Mon Jan 19 1970 10:26:46 GMT+0100, and the offset is small like 5 seconds', () => {
const decodedIdToken = {
iat: 1589206486, // Mon Jan 19 1970 10:26:46 GMT+0100 (Central European Standard Time)
};
const valueFalse = tokenValidationService.validateIdTokenIatMaxOffset(
decodedIdToken,
5,
false,
{ configId: 'configId1' }
);
expect(valueFalse).toEqual(false);
});
});
describe('validateSignatureIdToken', () => {
it('returns false if no kwtKeys are passed', async () => {
const valueFalse$ = tokenValidationService.validateSignatureIdToken(
'some-id-token',
null,
{ configId: 'configId1' }
);
valueFalse$.subscribe((valueFalse) => {
expect(valueFalse).toEqual(false);
});
});
it('returns true if no idToken is passed', async () => {
const valueFalse$ = tokenValidationService.validateSignatureIdToken(
null as any,
'some-jwt-keys',
{ configId: 'configId1' }
);
valueFalse$.subscribe((valueFalse) => {
expect(valueFalse).toEqual(true);
});
});
it('returns false if jwtkeys has no keys-property', async () => {
const valueFalse$ = tokenValidationService.validateSignatureIdToken(
'some-id-token',
{ notKeys: '' },
{ configId: 'configId1' }
);
valueFalse$.subscribe((valueFalse) => {
expect(valueFalse).toEqual(false);
});
});
it('returns false if header data has no header data', async () => {
vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({});
const jwtKeys = {
keys: 'someThing',
};
const valueFalse$ = tokenValidationService.validateSignatureIdToken(
'some-id-token',
jwtKeys,
{ configId: 'configId1' }
);
valueFalse$.subscribe((valueFalse) => {
expect(valueFalse).toEqual(false);
});
});
it('returns false if header data alg property does not exist in keyalgorithms', async () => {
vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({
alg: 'NOT SUPPORTED ALG',
});
const jwtKeys = {
keys: 'someThing',
};
const valueFalse$ = tokenValidationService.validateSignatureIdToken(
'some-id-token',
jwtKeys,
{ configId: 'configId1' }
);
valueFalse$.subscribe((valueFalse) => {
expect(valueFalse).toEqual(false);
});
});
it('returns false if header data has kid property and jwtKeys has same kid property but they are not valid with the token', (done) => {
const kid = '5626CE6A8F4F5FCD79C6642345282CA76D337548';
vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({
alg: 'RS256',
kid,
});
vi.spyOn(tokenHelperService, 'getSignatureFromToken').mockReturnValue('');
const jwtKeys = {
keys: [
{
kty: 'RSA',
use: 'sig',
kid,
x5t: 'VibOao9PX815xmQjRSgsp20zdUg',
e: 'AQAB',
n: 'uu3-HK4pLRHJHoEBzFhM516RWx6nybG5yQjH4NbKjfGQ8dtKy1BcGjqfMaEKF8KOK44NbAx7rtBKCO9EKNYkeFvcUzBzVeuu4jWG61XYdTekgv-Dh_Fj8245GocEkbvBbFW6cw-_N59JWqUuiCvb-EOfhcuubUcr44a0AQyNccYNpcXGRcMKy7_L1YhO0AMULqLDDVLFj5glh4TcJ2N5VnJedq1-_JKOxPqD1ni26UOQoWrW16G29KZ1_4Xxf2jX8TAq-4RJEHccdzgZVIO4F5B4MucMZGq8_jMCpiTUsUGDOAMA_AmjxIRHOtO5n6Pt0wofrKoAVhGh2sCTtaQf2Q',
x5c: [
'MIIDPzCCAiegAwIBAgIQF+HRVxLHII9IlOoQk6BxcjANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBzdHMuZGV2LmNlcnQuY29tMB4XDTE5MDIyMDEwMTA0M1oXDTM5MDIyMDEwMTkyOVowGzEZMBcGA1UEAwwQc3RzLmRldi5jZXJ0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrt/hyuKS0RyR6BAcxYTOdekVsep8mxuckIx+DWyo3xkPHbSstQXBo6nzGhChfCjiuODWwMe67QSgjvRCjWJHhb3FMwc1XrruI1hutV2HU3pIL/g4fxY/NuORqHBJG7wWxVunMPvzefSVqlLogr2/hDn4XLrm1HK+OGtAEMjXHGDaXFxkXDCsu/y9WITtADFC6iww1SxY+YJYeE3CdjeVZyXnatfvySjsT6g9Z4tulDkKFq1tehtvSmdf+F8X9o1/EwKvuESRB3HHc4GVSDuBeQeDLnDGRqvP4zAqYk1LFBgzgDAPwJo8SERzrTuZ+j7dMKH6yqAFYRodrAk7WkH9kCAwEAAaN/MH0wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAtBgNVHREEJjAkghBzdHMuZGV2LmNlcnQuY29tghBzdHMuZGV2LmNlcnQuY29tMB0GA1UdDgQWBBQuyHxWP3je6jGMOmOiY+hz47r36jANBgkqhkiG9w0BAQsFAAOCAQEAKEHG7Ga6nb2XiHXDc69KsIJwbO80+LE8HVJojvITILz3juN6/FmK0HmogjU6cYST7m1MyxsVhQQNwJASZ6haBNuBbNzBXfyyfb4kr62t1oDLNwhctHaHaM4sJSf/xIw+YO+Qf7BtfRAVsbM05+QXIi2LycGrzELiXu7KFM0E1+T8UOZ2Qyv7OlCb/pWkYuDgE4w97ox0MhDpvgluxZLpRanOLUCVGrfFaij7gRAhjYPUY3vAEcD8JcFBz1XijU8ozRO6FaG4qg8/JCe+VgoWsMDj3sKB9g0ob6KCyG9L2bdk99PGgvXDQvMYCpkpZzG3XsxOINPd5p0gc209ZOoxTg==',
],
alg: 'RS256',
},
],
};
const valueFalse$ = tokenValidationService.validateSignatureIdToken(
'someNOTMATCHINGIdToken',
jwtKeys,
{ configId: 'configId1' }
);
valueFalse$.subscribe((valueFalse) => {
expect(valueFalse).toEqual(false);
done();
});
});
it('should return true if valid input is provided', (done) => {
const idToken =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2IiwiYXVkIjoibXlfY2xpZW50X2lkIiwiZXhwIjoxMzExMjgxOTcwLCJpYXQiOjEzMTEyODA5NzAsIm5hbWUiOiJKYW5lIERvZSIsImdpdmVuX25hbWUiOiJKYW5lIiwiZmFtaWx5X25hbWUiOiJEb2UiLCJiaXJ0aGRhdGUiOiIxOTkwLTEwLTMxIiwiZW1haWwiOiJqYW5lZG9lQGV4YW1wbGUuY29tIiwicGljdHVyZSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vamFuZWRvZS9tZS5qcGcifQ.SY0ilps7yKYmYCc41zNOatfmAFhOtDYwuIT80qrHMl_4FEO2WFWSv-aDl4QfTSKY9A6MMP6xy0Z_8Kk7NeRwIV7FVScMLnPvVzs9pxza0e_rl6hmZLb5P5n4AEINwn46X9XmRB5W3EZO_x2LG65_g3NZFiPrzOC1Fs_6taJl7TfI8lOveYDoJyXCWYQMS3Oh5MM9S8W-Hc29_qJLH-kixm1S01qoICRPDGMRwhtAu1DHjwWQp9Ycfz6g3uyb7N1imBvI49t1CwWy02_mQ3g-7e7bOP1Ax2kgrwnJgsVBDULnyCZG9PE8T0CHZl_fErZtvbJJ0jdoZ1fyr48906am2w';
const idTokenParts = idToken.split('.');
const key = {
kty: 'RSA',
n: 'u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw',
e: 'AQAB',
alg: 'RS256',
kid: 'boop',
use: 'sig',
};
const jwtKeys = {
keys: [key],
};
vi.spyOn(tokenHelperService, 'getHeaderFromToken').mockReturnValue({
alg: 'RS256',
typ: 'JWT',
});
vi.spyOn(tokenHelperService, 'getSigningInputFromToken').mockReturnValue(
[idTokenParts[0], idTokenParts[1]].join('.')
);
vi.spyOn(tokenHelperService, 'getSignatureFromToken').mockReturnValue(
idTokenParts[2]
);
const valueTrue$ = tokenValidationService.validateSignatureIdToken(
idToken,
jwtKeys,
{ configId: 'configId1' }
);
valueTrue$.subscribe((valueTrue) => {
expect(valueTrue).toEqual(true);
done();
});
});
});
describe('validateIdTokenAtHash', () => {
it('returns true if sha is sha256 and generated hash equals atHash param', (done) => {
const accessToken = 'iGU3DhbPoDljiYtr0oepxi7zpT8BsjdU7aaXcdq-DPk';
const atHash = '-ODC_7Go_UIUTC8nP4k2cA';
const result$ = tokenValidationService.validateIdTokenAtHash(
accessToken,
atHash,
'256',
{ configId: 'configId1' }
);
result$.subscribe((result) => {
expect(result).toEqual(true);
done();
});
});
it('returns false if sha is sha256 and generated hash does not equal atHash param', async () => {
const accessToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
const atHash = 'bad';
const result = await lastValueFrom(
tokenValidationService.validateIdTokenAtHash(
accessToken,
atHash,
'256',
{ configId: 'configId1' }
)
);
expect(result).toEqual(false);
});
it('returns true if sha is sha256 and generated hash does equal atHash param', (done) => {
const accessToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
const atHash = 'good';
vi.spyOn(jwtWindowCryptoService, 'generateAtHash').mockReturnValues(
of('notEqualsGood'),
of('good')
);
const result$ = tokenValidationService.validateIdTokenAtHash(
accessToken,
atHash,
'256',
{ configId: 'configId1' }
);
result$.subscribe((result) => {
expect(result).toEqual(true);
done();
});
});
it('returns false if sha is sha384 and generated hash does not equal atHash param', (done) => {
const accessToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
const atHash = 'bad';
const result$ = tokenValidationService.validateIdTokenAtHash(
accessToken,
atHash,
'384',
{ configId: 'configId1' }
);
result$.subscribe((result) => {
expect(result).toEqual(false);
done();
});
});
it('returns false if sha is sha512 and generated hash does not equal atHash param', (done) => {
const accessToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
const atHash = 'bad';
const result$ = tokenValidationService.validateIdTokenAtHash(
accessToken,
atHash,
'512',
{ configId: 'configId1' }
);
result$.subscribe((result) => {
expect(result).toEqual(false);
done();
});
});
});
describe('validateStateFromHashCallback', () => {
it('returns true when state and localstate match', () => {
const result = tokenValidationService.validateStateFromHashCallback(
'sssd',
'sssd',
{ configId: 'configId1' }
);
expect(result).toEqual(true);
});
it('returns false when state and local state do not match', () => {
const result = tokenValidationService.validateStateFromHashCallback(
'sssd',
'bad',
{ configId: 'configId1' }
);
expect(result).toEqual(false);
});
});
describe('validateIdTokenExpNotExpired', () => {
it('returns false when getTokenExpirationDate returns null', () => {
vi.spyOn(tokenHelperService, 'getTokenExpirationDate').mockReturnValue(
null as unknown as Date
);
const notExpired = tokenValidationService.validateIdTokenExpNotExpired(
'idToken',
{ configId: 'configId1' },
0
);
expect(notExpired).toEqual(false);
});
it('returns false if token is not expired', () => {
const idToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
const notExpired = tokenValidationService.validateIdTokenExpNotExpired(
idToken,
{ configId: 'configId1' },
0
);
expect(notExpired).toEqual(false);
});
});
describe('validateAccessTokenNotExpired', () => {
const testCases = [
{
// Mon Jan 19 1970 10:26:50 GMT+0100,
date: new Date(1589210086),
offsetSeconds: 0,
expectedResult: false,
},
{
// Sun Nov 01 2550 00:00:00 GMT+0100
date: new Date(2550, 10),
offsetSeconds: 0,
expectedResult: true,
},
{
date: null,
offsetSeconds: 300,
expectedResult: true,
},
];
testCases.forEach(({ date, offsetSeconds, expectedResult }) => {
it(`returns ${expectedResult} if ${date} is given with an offset of ${offsetSeconds}`, () => {
const notExpired = tokenValidationService.validateAccessTokenNotExpired(
date as Date,
{ configId: 'configId1' },
offsetSeconds
);
expect(notExpired).toEqual(expectedResult);
});
});
});
describe('hasIdTokenExpired', () => {
it('returns true if token has expired', () => {
const idToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJleHAiOjE1ODkyMTAwODYsIm5iZiI6MTU4OTIwNjQ4NiwidmVyIjoiMS4wIiwiaXNzIjoiaHR0cHM6Ly9kYW1pZW5ib2QuYjJjbG9naW4uY29tL2EwOTU4ZjQ1LTE5NWItNDAzNi05MjU5LWRlMmY3ZTU5NGRiNi92Mi4wLyIsInN1YiI6ImY4MzZmMzgwLTNjNjQtNDgwMi04ZGJjLTAxMTk4MWMwNjhmNSIsImF1ZCI6ImYxOTM0YTZlLTk1OGQtNDE5OC05ZjM2LTYxMjdjZmM0Y2RiMyIsIm5vbmNlIjoiMDA3YzQxNTNiNmEwNTE3YzBlNDk3NDc2ZmIyNDk5NDhlYzVjbE92UVEiLCJpYXQiOjE1ODkyMDY0ODYsImF1dGhfdGltZSI6MTU4OTIwNjQ4NiwibmFtZSI6ImRhbWllbmJvZCIsImVtYWlscyI6WyJkYW1pZW5AZGFtaWVuYm9kLm9ubWljcm9zb2Z0LmNvbSJdLCJ0ZnAiOiJCMkNfMV9iMmNwb2xpY3lkYW1pZW4iLCJhdF9oYXNoIjoiWmswZktKU19wWWhPcE04SUJhMTJmdyJ9.E5Z-0kOzNU7LBkeVHHMyNoER8TUapGzUUfXmW6gVu4v6QMM5fQ4sJ7KC8PHh8lBFYiCnaDiTtpn3QytUwjXEFnLDAX5qcZT1aPoEgL_OmZMC-8y-4GyHp35l7VFD4iNYM9fJmLE8SYHTVl7eWPlXSyz37Ip0ciiV0Fd6eoksD_aVc-hkIqngDfE4fR8ZKfv4yLTNN_SfknFfuJbZ56yN-zIBL4GkuHsbQCBYpjtWQ62v98p1jO7NhHKV5JP2ec_Ge6oYc_bKTrE6OIX38RJ2rIm7zU16mtdjnl_350Nw3ytHcTPnA1VpP_VLElCfe83jr5aDHc_UQRYaAcWlOgvmVg';
const result = tokenValidationService.hasIdTokenExpired(idToken, {
configId: 'configId1',
});
expect(result).toBe(true);
});
it('returns false if token has not expired using offset', () => {
// expires 2050-02-12T08:02:30.823Z
const tokenExpires = new Date('2050-02-12T08:02:30.823Z');
const idToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTMxMTY5NTAsImV4cCI6MjUyODI2NTc1MCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.GHxRo23NghUTTeZx6VIzTSf05JEeEn7z9YYyFLxWv6M';
vi.spyOn(tokenHelperService, 'getTokenExpirationDate').mockReturnValue(
tokenExpires
);
const result = tokenValidationService.hasIdTokenExpired(idToken, {
configId: 'configId1',
});
expect(result).toBe(false);
});
});
});