@auth0/nextjs-auth0
Version:
Auth0 Next.js SDK
709 lines (708 loc) • 30.9 kB
JavaScript
import { describe, expect, it } from "vitest";
import { accessTokenSetFromTokenSet, compareScopes, findAccessTokenSet, mergeScopes, tokenSetFromAccessTokenSet } from "./token-set-helpers.js";
function createSessionData(sessionData = {}) {
return {
user: { sub: "user123", name: "Test User" },
internal: { sid: "session123", createdAt: Date.now() },
tokenSet: {
accessToken: "<my_access_token>",
expiresAt: Date.now() / 1000 + 3600
},
...sessionData
};
}
describe("token-set-helpers", () => {
describe("accessTokenSetFromTokenSet", () => {
it("should create an AccessTokenSet from a TokenSet", () => {
const session = createSessionData();
const options = {
audience: "<my_audience>"
};
expect(accessTokenSetFromTokenSet(session.tokenSet, options)).toEqual({
accessToken: session.tokenSet.accessToken,
expiresAt: session.tokenSet.expiresAt,
audience: options.audience,
scope: session.tokenSet.scope
});
});
});
describe("tokenSetFromAccessTokenSet", () => {
it("should merge an AccessTokenSet into a partial TokenSet", () => {
const accessTokenSet = {
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: "read:messages write:messages",
requestedScope: "read:messages write:messages delete:messages",
audience: "https://api.example.com"
};
const tokenSet = {
idToken: "<id_token>",
refreshToken: "<refresh_token>"
};
const result = tokenSetFromAccessTokenSet(accessTokenSet, tokenSet);
expect(result).toEqual({
idToken: "<id_token>",
refreshToken: "<refresh_token>",
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: "read:messages write:messages",
requestedScope: "read:messages write:messages delete:messages",
audience: "https://api.example.com"
});
});
it("should handle undefined accessTokenSet", () => {
const tokenSet = {
idToken: "<id_token>",
refreshToken: "<refresh_token>"
};
const result = tokenSetFromAccessTokenSet(undefined, tokenSet);
expect(result).toEqual({
idToken: "<id_token>",
refreshToken: "<refresh_token>",
accessToken: undefined,
expiresAt: undefined,
scope: undefined,
requestedScope: undefined,
audience: undefined
});
});
it("should handle empty tokenSet", () => {
const accessTokenSet = {
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: "read:messages",
audience: "https://api.example.com"
};
const result = tokenSetFromAccessTokenSet(accessTokenSet, {});
expect(result).toEqual({
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: "read:messages",
requestedScope: undefined,
audience: "https://api.example.com"
});
});
it("should override tokenSet properties with accessTokenSet values", () => {
const accessTokenSet = {
accessToken: "<new_access_token>",
expiresAt: 9999999999,
scope: "read:messages",
requestedScope: "read:messages write:messages",
audience: "https://api.example.com"
};
const tokenSet = {
accessToken: "<old_access_token>",
expiresAt: 1111111111,
scope: "old:scope",
requestedScope: "old:requested:scope",
audience: "https://old-api.example.com",
idToken: "<id_token>",
refreshToken: "<refresh_token>"
};
const result = tokenSetFromAccessTokenSet(accessTokenSet, tokenSet);
expect(result).toEqual({
idToken: "<id_token>",
refreshToken: "<refresh_token>",
accessToken: "<new_access_token>",
expiresAt: 9999999999,
scope: "read:messages",
requestedScope: "read:messages write:messages",
audience: "https://api.example.com"
});
});
it("should handle accessTokenSet without optional fields", () => {
const accessTokenSet = {
accessToken: "<access_token>",
expiresAt: 1234567890,
audience: "https://api.example.com"
};
const tokenSet = {
idToken: "<id_token>",
refreshToken: "<refresh_token>"
};
const result = tokenSetFromAccessTokenSet(accessTokenSet, tokenSet);
expect(result).toEqual({
idToken: "<id_token>",
refreshToken: "<refresh_token>",
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: undefined,
requestedScope: undefined,
audience: "https://api.example.com"
});
});
it("should preserve additional tokenSet properties", () => {
const accessTokenSet = {
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: "read:messages",
audience: "https://api.example.com"
};
const tokenSet = {
idToken: "<id_token>",
refreshToken: "<refresh_token>",
customProperty: "custom_value"
};
const result = tokenSetFromAccessTokenSet(accessTokenSet, tokenSet);
expect(result).toEqual({
idToken: "<id_token>",
refreshToken: "<refresh_token>",
customProperty: "custom_value",
accessToken: "<access_token>",
expiresAt: 1234567890,
scope: "read:messages",
requestedScope: undefined,
audience: "https://api.example.com"
});
});
it("should handle both undefined accessTokenSet and empty tokenSet", () => {
const result = tokenSetFromAccessTokenSet(undefined, {});
expect(result).toEqual({
accessToken: undefined,
expiresAt: undefined,
scope: undefined,
requestedScope: undefined,
audience: undefined
});
});
});
describe("findAccessTokenSet", () => {
it("should find the AccessTokenSet when it is the only entry", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should find the AccessTokenSet when it is not the only entry", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "c d",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet2, accessTokenSet]
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should find the AccessTokenSet when it is not the only exact match entry and requested scope is empty", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet, accessTokenSet2]
});
const options = {
scope: "",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should find the AccessTokenSet when the scope match partial", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "c d",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet2, accessTokenSet]
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should find the AccessTokenSet when the scope match exact", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "c d",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet2, accessTokenSet]
});
const options = {
scope: "a b",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should find the AccessTokenSet with the best match", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a",
audience: "<my_audience>"
};
const accessTokenSet3 = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet, accessTokenSet3, accessTokenSet2]
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet2);
});
it("should find the AccessTokenSet with the best match without exact match and ignore duplicates", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a a a a a b",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b c d",
audience: "<my_audience>"
};
const accessTokenSet3 = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet, accessTokenSet3, accessTokenSet2]
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should not find the AccessTokenSet when accessTokens is undefined", () => {
const session = createSessionData({
accessTokens: undefined
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBeUndefined();
});
it("should not find the AccessTokenSet when accessTokens is empty array", () => {
const session = createSessionData({
accessTokens: []
});
const options = {
scope: "a",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBeUndefined();
});
it("should not find the AccessTokenSet when no match", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "c",
audience: "<my_audience>"
};
expect(findAccessTokenSet(session, options)).toBeUndefined();
});
describe("with matchMode: 'scope'", () => {
it("should find exact match using scope field with strict comparison", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
requestedScope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a b",
audience: "<my_audience>",
matchMode: "scope"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should not find AccessTokenSet when scope has extra permissions (strict mode)", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b c",
requestedScope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a b",
audience: "<my_audience>",
matchMode: "scope"
};
// Should not match because strict mode requires exact match
expect(findAccessTokenSet(session, options)).toBeUndefined();
});
it("should not find AccessTokenSet when requesting more scopes than available", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
requestedScope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a b c",
audience: "<my_audience>",
matchMode: "scope"
};
// Should not match because actual scope doesn't have 'c'
expect(findAccessTokenSet(session, options)).toBeUndefined();
});
it("should match using scope field, not requestedScope field", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a",
requestedScope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a",
audience: "<my_audience>",
matchMode: "scope"
};
// Should match based on scope field "a", not requestedScope "a b c"
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should handle scope order differences with strict mode", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "b a",
requestedScope: "a b c",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a b",
audience: "<my_audience>",
matchMode: "scope"
};
// Should match because compareScopes handles order differences
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should find best match among multiple candidates in scope mode", () => {
const accessTokenSet1 = {
accessToken: "<my_custom_access_token_1>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a",
requestedScope: "a b c",
audience: "<my_audience>"
};
const accessTokenSet2 = {
accessToken: "<my_custom_access_token_2>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a",
requestedScope: "a b",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet1, accessTokenSet2]
});
const options = {
scope: "a",
audience: "<my_audience>",
matchMode: "scope"
};
// Should return the first matching one (both have same scope)
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet1);
});
it("should work with empty scope in strict mode", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "",
requestedScope: "a b",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "",
audience: "<my_audience>",
matchMode: "scope"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
it("should not match when requestedScope matches but scope does not", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "x y z",
requestedScope: "a b",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a b",
audience: "<my_audience>",
matchMode: "scope"
};
// Should not match because we're checking against scope field, not requestedScope
expect(findAccessTokenSet(session, options)).toBeUndefined();
});
it("should fall back to scope when requestedScope is undefined", () => {
const accessTokenSet = {
accessToken: "<my_custom_access_token>",
expiresAt: Date.now() / 1000 + 3600,
scope: "a b",
audience: "<my_audience>"
};
const session = createSessionData({
accessTokens: [accessTokenSet]
});
const options = {
scope: "a b",
audience: "<my_audience>",
matchMode: "scope"
};
expect(findAccessTokenSet(session, options)).toBe(accessTokenSet);
});
});
});
describe("compareScopes", () => {
it("should match scopes when more scopes are available", () => {
const scopes = "a b";
const requiredScopes = "a";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should not match scopes when more scopes are available and strict is true", () => {
const scopes = "a b";
const requiredScopes = "a";
expect(compareScopes(scopes, requiredScopes, { strict: true })).toBe(false);
});
it("should match exact scopes", () => {
const scopes = "a b";
const requiredScopes = "a b";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes when leading whitespaces in scopes", () => {
const scopes = " a b";
const requiredScopes = "a b";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes when trailing whitespaces in scopes", () => {
const scopes = "a b ";
const requiredScopes = "a b";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes when additional whitespaces in scopes", () => {
const scopes = "a b";
const requiredScopes = "a b";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes when leading whitespaces in requiredScopes", () => {
const scopes = "a b";
const requiredScopes = " a b";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes when trailing whitespaces in requiredScopes", () => {
const scopes = "a b";
const requiredScopes = "a b ";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes when additional whitespaces in requiredScopes", () => {
const scopes = "a b";
const requiredScopes = "a b";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match exact scopes in reverse order", () => {
const scopes = "a b";
const requiredScopes = "b a";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match when both empty", () => {
const scopes = "";
const requiredScopes = "";
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should match when both undefined", () => {
const scopes = undefined;
const requiredScopes = undefined;
expect(compareScopes(scopes, requiredScopes)).toBe(true);
});
it("should not match when scopes empty", () => {
const scopes = "";
const requiredScopes = "a b c d";
expect(compareScopes(scopes, requiredScopes)).toBe(false);
});
it("should not match when requiredScopes empty", () => {
const scopes = "a b";
const requiredScopes = "";
expect(compareScopes(scopes, requiredScopes)).toBe(false);
});
it("should not match when no scope included", () => {
const scopes = "a b";
const requiredScopes = "c d";
expect(compareScopes(scopes, requiredScopes)).toBe(false);
});
it("should not match when some scopes not included", () => {
const scopes = "a b";
const requiredScopes = "a b c d";
expect(compareScopes(scopes, requiredScopes)).toBe(false);
});
it("should not match when scopes is undefined and requiredScopes empty string", () => {
expect(compareScopes(undefined, "")).toBe(false);
});
});
describe("mergeScopes", () => {
it("should merge two scope strings without duplicates", () => {
const scopes1 = "read:messages write:messages";
const scopes2 = "delete:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages delete:messages");
});
it("should merge two scope strings with duplicates", () => {
const scopes1 = "read:messages write:messages";
const scopes2 = "read:messages delete:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages delete:messages");
});
it("should handle undefined scope strings", () => {
const scopes1 = "read:messages write:messages";
const scopes2 = undefined;
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages");
});
it("should handle null scope strings", () => {
const scopes1 = null;
const scopes2 = "read:messages write:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages");
});
it("should handle both undefined scope strings", () => {
const result = mergeScopes(undefined, undefined);
expect(result).toBe("");
});
it("should handle both null scope strings", () => {
const result = mergeScopes(null, null);
expect(result).toBe("");
});
it("should handle leading whitespace in scopes", () => {
const scopes1 = " read:messages write:messages";
const scopes2 = "delete:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages delete:messages");
});
it("should handle trailing whitespace in scopes", () => {
const scopes1 = "read:messages write:messages ";
const scopes2 = "delete:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages delete:messages");
});
it("should handle multiple consecutive spaces in scopes", () => {
const scopes1 = "read:messages write:messages";
const scopes2 = "delete:messages read:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages delete:messages");
});
it("should remove duplicate scopes", () => {
const scopes1 = "read:messages read:messages";
const scopes2 = "read:messages write:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages write:messages");
});
it("should handle empty string scopes", () => {
const scopes1 = "";
const scopes2 = "read:messages";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:messages");
});
it("should handle both empty string scopes", () => {
const result = mergeScopes("", "");
expect(result).toBe("");
});
it("should handle complex real-world scenario", () => {
const scopes1 = "openid profile email offline_access";
const scopes2 = "read:messages write:messages openid";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("openid profile email offline_access read:messages write:messages");
});
it("should handle scopes with unusual characters", () => {
const scopes1 = "read:user/profile write:user/settings";
const scopes2 = "delete:user/account read:user/profile";
const result = mergeScopes(scopes1, scopes2);
expect(result).toBe("read:user/profile write:user/settings delete:user/account");
});
});
});