Repository: authts/oidc-client-ts
Pull request #904 (2023-02-27 16:29:41)

add ClaimsService
This MR introduces the ClaimsService, no behavior changes. Taken idea from @marcoreni via #881

### Checklist

- [ ] This PR makes changes to the public API <!-- was the API report (docs/oidc-client-ts.api.md) updated by this PR? -->
- [ ] I have included links for closing relevant issue numbers



Patch:
diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md
index bf88d32a..6bd5b4ed 100644
--- a/docs/oidc-client-ts.api.md
+++ b/docs/oidc-client-ts.api.md
@@ -287,6 +287,10 @@ export interface OidcAddressClaim {
 // @public
 export class OidcClient {
     constructor(settings: OidcClientSettings);
+    // Warning: (ae-forgotten-export) The symbol "ClaimsService" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    protected readonly _claimsService: ClaimsService;
     // (undocumented)
     clearStaleState(): Promise<void>;
     // (undocumented)
diff --git a/src/ClaimsService.ts b/src/ClaimsService.ts
new file mode 100644
index 00000000..6437cf33
--- /dev/null
+++ b/src/ClaimsService.ts
@@ -0,0 +1,94 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+import type { JwtClaims } from "./Claims";
+import type { OidcClientSettingsStore } from "./OidcClientSettings";
+import type { UserProfile } from "./User";
+import { Logger } from "./utils";
+
+/**
+ * Protocol claims that could be removed by default from profile.
+ * Derived from the following sets of claims:
+ * - {@link https://datatracker.ietf.org/doc/html/rfc7519.html#section-4.1}
+ * - {@link https://openid.net/specs/openid-connect-core-1_0.html#IDToken}
+ * - {@link https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken}
+ *
+ * @internal
+ */
+const DefaultProtocolClaims = [
+    "nbf",
+    "jti",
+    "auth_time",
+    "nonce",
+    "acr",
+    "amr",
+    "azp",
+    "at_hash", // https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
+] as const;
+
+/**
+ * Protocol claims that should never be removed from profile.
+ * "sub" is needed internally and others should remain required as per the OIDC specs.
+ *
+ * @internal
+ */
+const InternalRequiredProtocolClaims = ["sub", "iss", "aud", "exp", "iat"];
+
+/**
+ * @internal
+ */
+export class ClaimsService {
+    protected readonly _logger = new Logger("ClaimsService");
+    public constructor(
+        protected readonly _settings: OidcClientSettingsStore,
+    ) {}
+
+    public filterProtocolClaims(claims: UserProfile): UserProfile {
+        const result = { ...claims };
+
+        if (this._settings.filterProtocolClaims) {
+            let protocolClaims;
+            if (Array.isArray(this._settings.filterProtocolClaims)) {
+                protocolClaims = this._settings.filterProtocolClaims;
+            } else {
+                protocolClaims = DefaultProtocolClaims;
+            }
+
+            for (const claim of protocolClaims) {
+                if (!InternalRequiredProtocolClaims.includes(claim)) {
+                    delete result[claim];
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public mergeClaims(claims1: UserProfile, claims2: JwtClaims): UserProfile {
+        const result = { ...claims1 };
+
+        for (const [claim, values] of Object.entries(claims2)) {
+            for (const value of Array.isArray(values) ? values : [values]) {
+                const previousValue = result[claim];
+                if (!previousValue) {
+                    result[claim] = value;
+                }
+                else if (Array.isArray(previousValue)) {
+                    if (!previousValue.includes(value)) {
+                        previousValue.push(value);
+                    }
+                }
+                else if (result[claim] !== value) {
+                    if (typeof value === "object" && this._settings.mergeClaims) {
+                        result[claim] = this.mergeClaims(previousValue as UserProfile, value);
+                    }
+                    else {
+                        result[claim] = [previousValue, value];
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/src/OidcClient.ts b/src/OidcClient.ts
index 666e5921..a3b8eb1e 100644
--- a/src/OidcClient.ts
+++ b/src/OidcClient.ts
@@ -14,6 +14,7 @@ import { SignoutResponse } from "./SignoutResponse";
 import { SigninState } from "./SigninState";
 import { State } from "./State";
 import { TokenClient } from "./TokenClient";
+import { ClaimsService } from "./ClaimsService";
 
 /**
  * @public
@@ -81,6 +82,7 @@ export class OidcClient {
     protected readonly _logger = new Logger("OidcClient");
 
     public readonly metadataService: MetadataService;
+    protected readonly _claimsService: ClaimsService;
     protected readonly _validator: ResponseValidator;
     protected readonly _tokenClient: TokenClient;
 
@@ -88,7 +90,8 @@ export class OidcClient {
         this.settings = new OidcClientSettingsStore(settings);
 
         this.metadataService = new MetadataService(this.settings);
-        this._validator = new ResponseValidator(this.settings, this.metadataService);
+        this._claimsService = new ClaimsService(this.settings);
+        this._validator = new ResponseValidator(this.settings, this.metadataService, this._claimsService);
         this._tokenClient = new TokenClient(this.settings, this.metadataService);
     }
 
diff --git a/src/ResponseValidator.ts b/src/ResponseValidator.ts
index 5f10f5d3..779cae92 100644
--- a/src/ResponseValidator.ts
+++ b/src/ResponseValidator.ts
@@ -13,35 +13,8 @@ import type { State } from "./State";
 import type { SignoutResponse } from "./SignoutResponse";
 import type { UserProfile } from "./User";
 import type { RefreshState } from "./RefreshState";
-import type { JwtClaims, IdTokenClaims } from "./Claims";
-
-/**
- * Protocol claims that could be removed by default from profile.
- * Derived from the following sets of claims:
- * - {@link https://datatracker.ietf.org/doc/html/rfc7519.html#section-4.1}
- * - {@link https://openid.net/specs/openid-connect-core-1_0.html#IDToken}
- * - {@link https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken}
- *
- * @internal
- */
-const DefaultProtocolClaims = [
-    "nbf",
-    "jti",
-    "auth_time",
-    "nonce",
-    "acr",
-    "amr",
-    "azp",
-    "at_hash", // https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
-] as const;
-
-/**
- * Protocol claims that should never be removed from profile.
- * "sub" is needed internally and others should remain required as per the OIDC specs.
- *
- * @internal
- */
-const InternalRequiredProtocolClaims = ["sub", "iss", "aud", "exp", "iat"];
+import type { IdTokenClaims } from "./Claims";
+import type { ClaimsService } from "./ClaimsService";
 
 /**
  * @internal
@@ -54,6 +27,7 @@ export class ResponseValidator {
     public constructor(
         protected readonly _settings: OidcClientSettingsStore,
         protected readonly _metadataService: MetadataService,
+        protected readonly _claimsService: ClaimsService,
     ) {}
 
     public async validateSigninResponse(response: SigninResponse, state: SigninState): Promise<void> {
@@ -175,7 +149,7 @@ export class ResponseValidator {
 
     protected async _processClaims(response: SigninResponse, skipUserInfo = false, validateSub = true): Promise<void> {
         const logger = this._logger.create("_processClaims");
-        response.profile = this._filterProtocolClaims(response.profile);
+        response.profile = this._claimsService.filterProtocolClaims(response.profile);
 
         if (skipUserInfo || !this._settings.loadUserInfo || !response.access_token) {
             logger.debug("not loading user info");
@@ -190,59 +164,10 @@ export class ResponseValidator {
             logger.throw(new Error("subject from UserInfo response does not match subject in ID Token"));
         }
 
-        response.profile = this._mergeClaims(response.profile, this._filterProtocolClaims(claims as IdTokenClaims));
+        response.profile = this._claimsService.mergeClaims(response.profile, this._claimsService.filterProtocolClaims(claims as IdTokenClaims));
         logger.debug("user info claims received, updated profile:", response.profile);
     }
 
-    protected _mergeClaims(claims1: UserProfile, claims2: JwtClaims): UserProfile {
-        const result = { ...claims1 };
-
-        for (const [claim, values] of Object.entries(claims2)) {
-            for (const value of Array.isArray(values) ? values : [values]) {
-                const previousValue = result[claim];
-                if (!previousValue) {
-                    result[claim] = value;
-                }
-                else if (Array.isArray(previousValue)) {
-                    if (!previousValue.includes(value)) {
-                        previousValue.push(value);
-                    }
-                }
-                else if (result[claim] !== value) {
-                    if (typeof value === "object" && this._settings.mergeClaims) {
-                        result[claim] = this._mergeClaims(previousValue as UserProfile, value);
-                    }
-                    else {
-                        result[claim] = [previousValue, value];
-                    }
-                }
-            }
-        }
-
-        return result;
-    }
-
-    protected _filterProtocolClaims(claims: UserProfile): UserProfile {
-        const result = { ...claims };
-
-        if (this._settings.filterProtocolClaims) {
-            let protocolClaims;
-            if (Array.isArray(this._settings.filterProtocolClaims)) {
-                protocolClaims = this._settings.filterProtocolClaims;
-            } else {
-                protocolClaims = DefaultProtocolClaims;
-            }
-
-            for (const claim of protocolClaims) {
-                if (!InternalRequiredProtocolClaims.includes(claim)) {
-                    delete result[claim];
-                }
-            }
-        }
-
-        return result;
-    }
-
     protected async _processCode(response: SigninResponse, state: SigninState): Promise<void> {
         const logger = this._logger.create("_processCode");
         if (response.code) {


Test patch:
diff --git a/src/ClaimsService.test.ts b/src/ClaimsService.test.ts
new file mode 100644
index 00000000..97ac7e66
--- /dev/null
+++ b/src/ClaimsService.test.ts
@@ -0,0 +1,352 @@
+// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
+
+import { ClaimsService } from "./ClaimsService";
+import type { OidcClientSettingsStore } from "./OidcClientSettings";
+import type { UserProfile } from "./User";
+
+describe("ClaimsService", () => {
+    let settings: OidcClientSettingsStore;
+    let subject: ClaimsService;
+
+    beforeEach(() => {
+        settings = {
+            authority: "op",
+            client_id: "client",
+            loadUserInfo: true,
+        } as OidcClientSettingsStore;
+
+        subject = new ClaimsService(settings);
+    });
+
+    afterEach(() => {
+        jest.restoreAllMocks();
+    });
+
+    describe("filterProtocolClaims", () => {
+        it("should filter protocol claims if enabled on settings", () => {
+            // arrange
+            Object.assign(settings, { filterProtocolClaims: true });
+            const claims = {
+                foo: 1,
+                bar: "test",
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                iat: 5,
+                exp: 20,
+                nbf: 10,
+                at_hash: "athash",
+            };
+
+            // act
+            const result = subject.filterProtocolClaims(claims);
+
+            // assert
+            expect(result).toEqual({
+                foo: 1,
+                bar: "test",
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                iat: 5,
+                exp: 20,
+            });
+        });
+
+        it("should not filter protocol claims if not enabled on settings", () => {
+            // arrange
+            Object.assign(settings, { filterProtocolClaims: false });
+            const claims = {
+                foo: 1,
+                bar: "test",
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                at_hash: "athash",
+                iat: 5,
+                nbf: 10,
+                exp: 20,
+            };
+
+            // act
+            const result = subject.filterProtocolClaims(claims);
+
+            // assert
+            expect(result).toEqual({
+                foo: 1,
+                bar: "test",
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                at_hash: "athash",
+                iat: 5,
+                nbf: 10,
+                exp: 20,
+            });
+        });
+
+        it("should filter protocol claims if specified in settings", () => {
+            // arrange
+            Object.assign(settings, {
+                filterProtocolClaims: ["foo", "bar", "role", "nbf", "email"],
+            });
+            const claims = {
+                foo: 1,
+                bar: "test",
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                iat: 5,
+                exp: 20,
+                nbf: 10,
+                at_hash: "athash",
+            };
+
+            // act
+            const result = subject.filterProtocolClaims(claims);
+
+            // assert
+            expect(result).toEqual({
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                iat: 5,
+                exp: 20,
+                at_hash: "athash",
+            });
+        });
+
+        it("should filter only protocol claims defined by default by the library", () => {
+            // arrange
+            Object.assign(settings, { filterProtocolClaims: true });
+            const defaultProtocolClaims = {
+                nbf: 3,
+                jti: "jti",
+                auth_time: 123,
+                nonce: "nonce",
+                acr: "acr",
+                amr: "amr",
+                azp: "azp",
+                at_hash: "athash",
+            };
+            const claims = {
+                foo: 1,
+                bar: "test",
+                aud: "some_aud",
+                iss: "issuer",
+                sub: "123",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                iat: 5,
+                exp: 20,
+            };
+
+            // act
+            const result = subject.filterProtocolClaims({
+                ...defaultProtocolClaims,
+                ...claims,
+            });
+
+            // assert
+            expect(result).toEqual(claims);
+        });
+
+        it("should not filter protocol claims that are required by the library", () => {
+            // arrange
+            Object.assign(settings, { filterProtocolClaims: true });
+            const internalRequiredProtocolClaims = {
+                sub: "sub",
+                iss: "issuer",
+                aud: "some_aud",
+                exp: 20,
+                iat: 5,
+            };
+            const claims = {
+                foo: 1,
+                bar: "test",
+                email: "foo@gmail.com",
+                role: ["admin", "dev"],
+                nbf: 10,
+            };
+
+            // act
+            let items = { ...internalRequiredProtocolClaims, ...claims };
+            let result = subject.filterProtocolClaims(items);
+
+            // assert
+            // nbf is part of the claims that should be filtered by the library by default, so we need to remove it
+            delete (items as Partial<typeof items>).nbf;
+            expect(result).toEqual(items);
+
+            // ... even if specified in settings
+
+            // arrange
+            Object.assign(settings, {
+                filterProtocolClaims: ["sub", "iss", "aud", "exp", "iat"],
+            });
+
+            // act
+            items = { ...internalRequiredProtocolClaims, ...claims };
+            result = subject.filterProtocolClaims(items);
+
+            // assert
+            expect(result).toEqual(items);
+        });
+    });
+
+    describe("mergeClaims", () => {
+        it("should merge claims", () => {
+            // arrange
+            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
+            const c2 = { c: "carrot" };
+
+            // act
+            const result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({ a: "apple", c: "carrot", b: "banana" });
+        });
+
+        it("should not merge claims when claim types are objects", () => {
+            // arrange
+            const c1 = {
+                custom: { apple: "foo", pear: "bar" },
+            } as unknown as UserProfile;
+            const c2 = {
+                custom: { apple: "foo", orange: "peel" },
+                b: "banana",
+            };
+
+            // act
+            const result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({
+                custom: [
+                    { apple: "foo", pear: "bar" },
+                    { apple: "foo", orange: "peel" },
+                ],
+                b: "banana",
+            });
+        });
+
+        it("should merge claims when claim types are objects when mergeClaims settings is true", () => {
+            // arrange
+            Object.assign(settings, { mergeClaims: true });
+
+            const c1 = {
+                custom: { apple: "foo", pear: "bar" },
+            } as unknown as UserProfile;
+            const c2 = {
+                custom: { apple: "foo", orange: "peel" },
+                b: "banana",
+            };
+
+            // act
+            const result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({
+                custom: { apple: "foo", pear: "bar", orange: "peel" },
+                b: "banana",
+            });
+        });
+
+        it("should merge same claim types into array", () => {
+            // arrange
+            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
+            const c2 = { a: "carrot" };
+
+            // act
+            const result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({ a: ["apple", "carrot"], b: "banana" });
+        });
+
+        it("should merge arrays of same claim types into array", () => {
+            // arrange
+            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
+            const c2 = { a: ["carrot", "durian"] };
+
+            // act
+            let result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({
+                a: ["apple", "carrot", "durian"],
+                b: "banana",
+            });
+
+            // arrange
+            const d1 = {
+                a: ["apple", "carrot"],
+                b: "banana",
+            } as unknown as UserProfile;
+            const d2 = { a: ["durian"] };
+
+            // act
+            result = subject.mergeClaims(d1, d2);
+
+            // assert
+            expect(result).toEqual({
+                a: ["apple", "carrot", "durian"],
+                b: "banana",
+            });
+
+            // arrange
+            const e1 = {
+                a: ["apple", "carrot"],
+                b: "banana",
+            } as unknown as UserProfile;
+            const e2 = { a: "durian" };
+
+            // act
+            result = subject.mergeClaims(e1, e2);
+
+            // assert
+            expect(result).toEqual({
+                a: ["apple", "carrot", "durian"],
+                b: "banana",
+            });
+        });
+
+        it("should remove duplicates when producing arrays", () => {
+            // arrange
+            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
+            const c2 = { a: ["apple", "durian"] };
+
+            // act
+            const result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({ a: ["apple", "durian"], b: "banana" });
+        });
+
+        it("should not add if already present in array", () => {
+            // arrange
+            const c1 = {
+                a: ["apple", "durian"],
+                b: "banana",
+            } as unknown as UserProfile;
+            const c2 = { a: "apple" };
+
+            // act
+            const result = subject.mergeClaims(c1, c2);
+
+            // assert
+            expect(result).toEqual({ a: ["apple", "durian"], b: "banana" });
+        });
+    });
+});
diff --git a/src/ResponseValidator.test.ts b/src/ResponseValidator.test.ts
index 4efee788..7fb52b12 100644
--- a/src/ResponseValidator.test.ts
+++ b/src/ResponseValidator.test.ts
@@ -5,10 +5,10 @@ import { JwtUtils } from "./utils";
 import { ErrorResponse } from "./errors";
 import { ResponseValidator } from "./ResponseValidator";
 import { MetadataService } from "./MetadataService";
+import { ClaimsService } from "./ClaimsService";
 import type { SigninState } from "./SigninState";
 import type { SigninResponse } from "./SigninResponse";
 import type { SignoutResponse } from "./SignoutResponse";
-import type { UserProfile } from "./User";
 import type { OidcClientSettingsStore } from "./OidcClientSettings";
 import { mocked } from "jest-mock";
 
@@ -17,6 +17,7 @@ describe("ResponseValidator", () => {
     let stubResponse: SigninResponse & SignoutResponse;
     let settings: OidcClientSettingsStore;
     let metadataService: MetadataService;
+    let claimsService: ClaimsService;
     let subject: ResponseValidator;
 
     beforeEach(() => {
@@ -38,7 +39,8 @@ describe("ResponseValidator", () => {
         } as OidcClientSettingsStore;
         metadataService = new MetadataService(settings);
 
-        subject = new ResponseValidator(settings, metadataService);
+        claimsService = new ClaimsService(settings);
+        subject = new ResponseValidator(settings, metadataService, claimsService);
         jest.spyOn(subject["_tokenClient"], "exchangeCode").mockResolvedValue(
             {},
         );
@@ -710,331 +712,4 @@ describe("ResponseValidator", () => {
             });
         });
     });
-
-    describe("_mergeClaims", () => {
-        it("should merge claims", () => {
-            // arrange
-            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
-            const c2 = { c: "carrot" };
-
-            // act
-            const result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({ a: "apple", c: "carrot", b: "banana" });
-        });
-
-        it("should not merge claims when claim types are objects", () => {
-            // arrange
-            const c1 = {
-                custom: { apple: "foo", pear: "bar" },
-            } as unknown as UserProfile;
-            const c2 = {
-                custom: { apple: "foo", orange: "peel" },
-                b: "banana",
-            };
-
-            // act
-            const result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({
-                custom: [
-                    { apple: "foo", pear: "bar" },
-                    { apple: "foo", orange: "peel" },
-                ],
-                b: "banana",
-            });
-        });
-
-        it("should merge claims when claim types are objects when mergeClaims settings is true", () => {
-            // arrange
-            Object.assign(settings, { mergeClaims: true });
-
-            const c1 = {
-                custom: { apple: "foo", pear: "bar" },
-            } as unknown as UserProfile;
-            const c2 = {
-                custom: { apple: "foo", orange: "peel" },
-                b: "banana",
-            };
-
-            // act
-            const result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({
-                custom: { apple: "foo", pear: "bar", orange: "peel" },
-                b: "banana",
-            });
-        });
-
-        it("should merge same claim types into array", () => {
-            // arrange
-            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
-            const c2 = { a: "carrot" };
-
-            // act
-            const result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({ a: ["apple", "carrot"], b: "banana" });
-        });
-
-        it("should merge arrays of same claim types into array", () => {
-            // arrange
-            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
-            const c2 = { a: ["carrot", "durian"] };
-
-            // act
-            let result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({
-                a: ["apple", "carrot", "durian"],
-                b: "banana",
-            });
-
-            // arrange
-            const d1 = {
-                a: ["apple", "carrot"],
-                b: "banana",
-            } as unknown as UserProfile;
-            const d2 = { a: ["durian"] };
-
-            // act
-            result = subject["_mergeClaims"](d1, d2);
-
-            // assert
-            expect(result).toEqual({
-                a: ["apple", "carrot", "durian"],
-                b: "banana",
-            });
-
-            // arrange
-            const e1 = {
-                a: ["apple", "carrot"],
-                b: "banana",
-            } as unknown as UserProfile;
-            const e2 = { a: "durian" };
-
-            // act
-            result = subject["_mergeClaims"](e1, e2);
-
-            // assert
-            expect(result).toEqual({
-                a: ["apple", "carrot", "durian"],
-                b: "banana",
-            });
-        });
-
-        it("should remove duplicates when producing arrays", () => {
-            // arrange
-            const c1 = { a: "apple", b: "banana" } as unknown as UserProfile;
-            const c2 = { a: ["apple", "durian"] };
-
-            // act
-            const result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({ a: ["apple", "durian"], b: "banana" });
-        });
-
-        it("should not add if already present in array", () => {
-            // arrange
-            const c1 = {
-                a: ["apple", "durian"],
-                b: "banana",
-            } as unknown as UserProfile;
-            const c2 = { a: "apple" };
-
-            // act
-            const result = subject["_mergeClaims"](c1, c2);
-
-            // assert
-            expect(result).toEqual({ a: ["apple", "durian"], b: "banana" });
-        });
-    });
-
-    describe("_filterProtocolClaims", () => {
-        it("should filter protocol claims if enabled on settings", () => {
-            // arrange
-            Object.assign(settings, { filterProtocolClaims: true });
-            const claims = {
-                foo: 1,
-                bar: "test",
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                iat: 5,
-                exp: 20,
-                nbf: 10,
-                at_hash: "athash",
-            };
-
-            // act
-            const result = subject["_filterProtocolClaims"](claims);
-
-            // assert
-            expect(result).toEqual({
-                foo: 1,
-                bar: "test",
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                iat: 5,
-                exp: 20,
-            });
-        });
-
-        it("should not filter protocol claims if not enabled on settings", () => {
-            // arrange
-            Object.assign(settings, { filterProtocolClaims: false });
-            const claims = {
-                foo: 1,
-                bar: "test",
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                at_hash: "athash",
-                iat: 5,
-                nbf: 10,
-                exp: 20,
-            };
-
-            // act
-            const result = subject["_filterProtocolClaims"](claims);
-
-            // assert
-            expect(result).toEqual({
-                foo: 1,
-                bar: "test",
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                at_hash: "athash",
-                iat: 5,
-                nbf: 10,
-                exp: 20,
-            });
-        });
-
-        it("should filter protocol claims if specified in settings", () => {
-            // arrange
-            Object.assign(settings, {
-                filterProtocolClaims: ["foo", "bar", "role", "nbf", "email"],
-            });
-            const claims = {
-                foo: 1,
-                bar: "test",
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                iat: 5,
-                exp: 20,
-                nbf: 10,
-                at_hash: "athash",
-            };
-
-            // act
-            const result = subject["_filterProtocolClaims"](claims);
-
-            // assert
-            expect(result).toEqual({
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                iat: 5,
-                exp: 20,
-                at_hash: "athash",
-            });
-        });
-
-        it("should filter only protocol claims defined by default by the library", () => {
-            // arrange
-            Object.assign(settings, { filterProtocolClaims: true });
-            const defaultProtocolClaims = {
-                nbf: 3,
-                jti: "jti",
-                auth_time: 123,
-                nonce: "nonce",
-                acr: "acr",
-                amr: "amr",
-                azp: "azp",
-                at_hash: "athash",
-            };
-            const claims = {
-                foo: 1,
-                bar: "test",
-                aud: "some_aud",
-                iss: "issuer",
-                sub: "123",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                iat: 5,
-                exp: 20,
-            };
-
-            // act
-            const result = subject["_filterProtocolClaims"]({
-                ...defaultProtocolClaims,
-                ...claims,
-            });
-
-            // assert
-            expect(result).toEqual(claims);
-        });
-
-        it("should not filter protocol claims that are required by the library", () => {
-            // arrange
-            Object.assign(settings, { filterProtocolClaims: true });
-            const internalRequiredProtocolClaims = {
-                sub: "sub",
-                iss: "issuer",
-                aud: "some_aud",
-                exp: 20,
-                iat: 5,
-            };
-            const claims = {
-                foo: 1,
-                bar: "test",
-                email: "foo@gmail.com",
-                role: ["admin", "dev"],
-                nbf: 10,
-            };
-
-            // act
-            let items = { ...internalRequiredProtocolClaims, ...claims };
-            let result = subject["_filterProtocolClaims"](items);
-
-            // assert
-            // nbf is part of the claims that should be filtered by the library by default, so we need to remove it
-            delete (items as Partial<typeof items>).nbf;
-            expect(result).toEqual(items);
-
-            // ... even if specified in settings
-
-            // arrange
-            Object.assign(settings, {
-                filterProtocolClaims: ["sub", "iss", "aud", "exp", "iat"],
-            });
-
-            // act
-            items = { ...internalRequiredProtocolClaims, ...claims };
-            result = subject["_filterProtocolClaims"](items);
-
-            // assert
-            expect(result).toEqual(items);
-        });
-    });
 });