Skip to content

Commit

Permalink
AAE-26321 Custom JWT storage service optional injection token (#10292)
Browse files Browse the repository at this point in the history
* AAE-26321 Custom JWT storage service optional injection token

* AAE-26321 custom storage service test

* AAE-26321 Use only the injection token with factory

* AAE-26321 improve custom storage test
  • Loading branch information
wojd0 authored Oct 9, 2024
1 parent 9a9a9db commit 9e96fed
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 60 deletions.
127 changes: 76 additions & 51 deletions lib/core/src/lib/auth/services/jwt-helper.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,34 @@
* limitations under the License.
*/

import { JwtHelperService } from './jwt-helper.service';
import { JWT_STORAGE_SERVICE, JwtHelperService } from './jwt-helper.service';
import { mockToken } from '../mock/jwt-helper.service.spec';
import { TestBed } from '@angular/core/testing';
import { StorageService } from '../../common';
import { OAuthStorage } from 'angular-oauth2-oidc';

const mockStorage = {
access_token: 'my-access_token',
id_token: 'my-id_token',
getItem(key: string) {
return this[key];
}
};

const mockCustomStorage = {
access_token: 'my-custom-access_token',
id_token: 'my-custom-id_token',
getItem(key: string) {
return this[key];
}
};

describe('JwtHelperService', () => {

let jwtHelperService: JwtHelperService;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [JwtHelperService]
providers: [JwtHelperService, { provide: StorageService, useValue: mockStorage }]
});
jwtHelperService = TestBed.inject(JwtHelperService);
});
Expand All @@ -44,96 +61,104 @@ describe('JwtHelperService', () => {
});

describe('RealmRole ', () => {

it('Should be true if the realm_access contains the single role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');

spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role1'] }
});

const result = jwtHelperService.hasRealmRole('role1');
expect(result).toBeTruthy();
});

it('Should be true if the realm_access contains at least one of the roles', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');

spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role1'] }
});

const result = jwtHelperService.hasRealmRoles(['role1', 'role2']);
expect(result).toBeTruthy();
});

it('Should be false if the realm_access does not contain the role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role3'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role3'] }
});
const result = jwtHelperService.hasRealmRole('role1');
expect(result).toBeFalsy();
});

it('Should be false if the realm_access does not contain at least one of the roles', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
realm_access: { roles: ['role1'] }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
realm_access: { roles: ['role1'] }
});
const result = jwtHelperService.hasRealmRoles(['role3', 'role2']);
expect(result).toBeFalsy();
});
});
});

describe('ClientRole ', () => {

it('Should be true if the resource_access contains the single role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');

spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role1'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { fakeApp: { roles: ['role1'] } }
});

const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1']);
expect(result).toBeTruthy();
});

it('Should be true if the resource_access contains at least one of the roles', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');

spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role1'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { fakeApp: { roles: ['role1'] } }
});

const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
expect(result).toBeTruthy();
});

it('Should be false if the resource_access does not contain the role', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { fakeApp: { roles: ['role3'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { fakeApp: { roles: ['role3'] } }
});
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
expect(result).toBeFalsy();
});

it('Should be false if the resource_access does not contain the client role related to the app', () => {
spyOn(jwtHelperService, 'getAccessToken').and.returnValue('my-access_token');
spyOn(jwtHelperService, 'decodeToken').and.returnValue(
{
resource_access: { anotherFakeApp: { roles: ['role1'] } }
});
spyOn(jwtHelperService, 'decodeToken').and.returnValue({
resource_access: { anotherFakeApp: { roles: ['role1'] } }
});
const result = jwtHelperService.hasRealmRolesForClientRole('fakeApp', ['role1', 'role2']);
expect(result).toBeFalsy();
});
});
});
});

describe('JwtHelperService with custom storage service', () => {
let jwtHelperService: JwtHelperService;
let defaultStorage: StorageService;
let customStorage: OAuthStorage;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
JwtHelperService,
{ provide: StorageService, useValue: mockStorage },
{ provide: JWT_STORAGE_SERVICE, useValue: mockCustomStorage }
]
});
jwtHelperService = TestBed.inject(JwtHelperService);
defaultStorage = TestBed.inject(StorageService);
customStorage = TestBed.inject(JWT_STORAGE_SERVICE);
});

it('should use the custom storage service', () => {
const customStorageGetItemSpy = spyOn(customStorage, 'getItem').and.callThrough();
const defaultStorageGetItemSpy = spyOn(defaultStorage, 'getItem').and.callThrough();
const result = jwtHelperService.getIdToken();

expect(customStorage).toBeDefined();
expect(customStorageGetItemSpy).toHaveBeenCalled();
expect(defaultStorageGetItemSpy).not.toHaveBeenCalled();
expect(result).toBe('my-custom-id_token');
});
});
22 changes: 13 additions & 9 deletions lib/core/src/lib/auth/services/jwt-helper.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
* limitations under the License.
*/

import { Injectable } from '@angular/core';
import { inject, Injectable, InjectionToken } from '@angular/core';
import { OAuthStorage } from 'angular-oauth2-oidc';
import { StorageService } from '../../common/services/storage.service';

export const JWT_STORAGE_SERVICE = new InjectionToken<OAuthStorage>('JWT_STORAGE_SERVICE', {
providedIn: 'root',
factory: () => inject(StorageService)
});

@Injectable({
providedIn: 'root'
})
export class JwtHelperService {

static USER_NAME = 'name';
static FAMILY_NAME = 'family_name';
static GIVEN_NAME = 'given_name';
Expand All @@ -34,8 +39,7 @@ export class JwtHelperService {
static USER_PREFERRED_USERNAME = 'preferred_username';
static HXP_AUTHORIZATION = 'hxp_authorization';

constructor(private storageService: StorageService) {
}
private storageService: OAuthStorage = inject(JWT_STORAGE_SERVICE);

/**
* Decodes a JSON web token into a JS object.
Expand Down Expand Up @@ -85,7 +89,7 @@ export class JwtHelperService {
* @param key Key name of the field to retrieve
* @returns Value from the token
*/
getValueFromLocalToken<T>(key: string): T {
getValueFromLocalToken<T>(key: string): T {
return this.getValueFromToken(this.getAccessToken(), key) || this.getValueFromToken(this.getIdToken(), key);
}

Expand Down Expand Up @@ -114,7 +118,7 @@ export class JwtHelperService {
* @param key Key name of the field to retrieve
* @returns Value from the token
*/
getValueFromLocalIdToken<T>(key: string): T {
getValueFromLocalIdToken<T>(key: string): T {
return this.getValueFromToken(this.getIdToken(), key);
}

Expand All @@ -123,7 +127,7 @@ export class JwtHelperService {
*
* @returns id token
*/
getIdToken(): string {
getIdToken(): string {
return this.storageService.getItem(JwtHelperService.USER_ID_TOKEN);
}

Expand Down Expand Up @@ -186,7 +190,7 @@ export class JwtHelperService {
* @param rolesToCheck List of role names to check
* @returns True if it contains at least one of the given roles, false otherwise
*/
hasRealmRoles(rolesToCheck: string []): boolean {
hasRealmRoles(rolesToCheck: string[]): boolean {
return rolesToCheck.some((currentRole) => this.hasRealmRole(currentRole));
}

Expand All @@ -197,7 +201,7 @@ export class JwtHelperService {
* @param rolesToCheck List of role names to check
* @returns True if it contains at least one of the given roles, false otherwise
*/
hasRealmRolesForClientRole(clientName: string, rolesToCheck: string []): boolean {
hasRealmRolesForClientRole(clientName: string, rolesToCheck: string[]): boolean {
return rolesToCheck.some((currentRole) => this.hasClientRole(clientName, currentRole));
}

Expand Down

0 comments on commit 9e96fed

Please sign in to comment.