UNPKG

@auth0/nextjs-auth0

Version:
249 lines (248 loc) 12.7 kB
import { NextResponse } from "next/server.js"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AccessTokenError, AccessTokenErrorCode } from "../errors/index.js"; import { AuthClient } from "./auth-client.js"; // Import the actual class for spyOn import { Auth0Client } from "./client.js"; // Define ENV_VARS at the top level for broader scope const ENV_VARS = { DOMAIN: "AUTH0_DOMAIN", CLIENT_ID: "AUTH0_CLIENT_ID", CLIENT_SECRET: "AUTH0_CLIENT_SECRET", CLIENT_ASSERTION_SIGNING_KEY: "AUTH0_CLIENT_ASSERTION_SIGNING_KEY", APP_BASE_URL: "APP_BASE_URL", SECRET: "AUTH0_SECRET", SCOPE: "AUTH0_SCOPE" }; describe("Auth0Client", () => { // Store original env vars const originalEnv = { ...process.env }; // Clear env vars before each test beforeEach(() => { vi.resetModules(); // Clear all environment variables that might affect the tests delete process.env[ENV_VARS.DOMAIN]; delete process.env[ENV_VARS.CLIENT_ID]; delete process.env[ENV_VARS.CLIENT_SECRET]; delete process.env[ENV_VARS.CLIENT_ASSERTION_SIGNING_KEY]; delete process.env[ENV_VARS.APP_BASE_URL]; delete process.env[ENV_VARS.SECRET]; delete process.env[ENV_VARS.SCOPE]; }); // Restore env vars after each test afterEach(() => { process.env = { ...originalEnv }; vi.restoreAllMocks(); // Restore mocks created within tests/beforeEach }); describe("constructor validation", () => { it("should accept clientSecret as authentication method", () => { // Set required environment variables with clientSecret process.env[ENV_VARS.DOMAIN] = "env.auth0.com"; process.env[ENV_VARS.CLIENT_ID] = "env_client_id"; process.env[ENV_VARS.CLIENT_SECRET] = "env_client_secret"; process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.com"; process.env[ENV_VARS.SECRET] = "env_secret"; // Should not throw const client = new Auth0Client(); // The client should be instantiated successfully expect(client).toBeInstanceOf(Auth0Client); }); it("should accept clientAssertionSigningKey as authentication method", () => { // Set required environment variables with clientAssertionSigningKey instead of clientSecret process.env[ENV_VARS.DOMAIN] = "env.auth0.com"; process.env[ENV_VARS.CLIENT_ID] = "env_client_id"; process.env[ENV_VARS.CLIENT_ASSERTION_SIGNING_KEY] = "some-signing-key"; process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.com"; process.env[ENV_VARS.SECRET] = "env_secret"; // Should not throw const client = new Auth0Client(); // The client should be instantiated successfully expect(client).toBeInstanceOf(Auth0Client); }); it("should prioritize options over environment variables", () => { // Set environment variables process.env[ENV_VARS.DOMAIN] = "env.auth0.com"; process.env[ENV_VARS.CLIENT_ID] = "env_client_id"; process.env[ENV_VARS.CLIENT_SECRET] = "env_client_secret"; process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.com"; process.env[ENV_VARS.SECRET] = "env_secret"; // Provide conflicting options const options = { domain: "options.auth0.com", clientId: "options_client_id", clientSecret: "options_client_secret", appBaseUrl: "https://options-app.com", secret: "options_secret" }; // Mock the validateAndExtractRequiredOptions to verify which values are used const mockValidateAndExtractRequiredOptions = vi .fn() .mockReturnValue(options); const originalValidateAndExtractRequiredOptions = Auth0Client.prototype["validateAndExtractRequiredOptions"]; Auth0Client.prototype["validateAndExtractRequiredOptions"] = mockValidateAndExtractRequiredOptions; try { new Auth0Client(options); // Check that validateAndExtractRequiredOptions was called with our options expect(mockValidateAndExtractRequiredOptions).toHaveBeenCalledWith(options); // The first argument of the first call should be our options object const passedOptions = mockValidateAndExtractRequiredOptions.mock.calls[0][0]; expect(passedOptions.domain).toBe("options.auth0.com"); expect(passedOptions.clientId).toBe("options_client_id"); } finally { // Restore the original method Auth0Client.prototype["validateAndExtractRequiredOptions"] = originalValidateAndExtractRequiredOptions; } }); }); describe("getAccessToken", () => { const mockSession = { user: { sub: "user123" }, tokenSet: { accessToken: "old_access_token", idToken: "old_id_token", refreshToken: "old_refresh_token", expiresAt: Date.now() / 1000 - 3600 // Expired }, internal: { sid: "mock_sid", createdAt: Date.now() / 1000 - 7200 // Some time in the past }, createdAt: Date.now() / 1000 }; // Restore original mock for refreshed token set const mockRefreshedTokenSet = { accessToken: "new_access_token", idToken: "new_id_token", refreshToken: "new_refresh_token", expiresAt: Date.now() / 1000 + 3600, // Not expired scope: "openid profile email" }; let client; let mockGetSession; let mockSaveToSession; let mockGetTokenSet; // Re-declare mockGetTokenSet beforeEach(() => { // Reset mocks specifically if vi.restoreAllMocks isn't enough // vi.resetAllMocks(); // Alternative to restoreAllMocks in afterEach // Set necessary environment variables process.env[ENV_VARS.DOMAIN] = "test.auth0.com"; process.env[ENV_VARS.CLIENT_ID] = "test_client_id"; process.env[ENV_VARS.CLIENT_SECRET] = "test_client_secret"; process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.test"; process.env[ENV_VARS.SECRET] = "test_secret"; client = new Auth0Client(); // Mock internal methods of Auth0Client mockGetSession = vi .spyOn(Auth0Client.prototype, "getSession") .mockResolvedValue(mockSession); mockSaveToSession = vi .spyOn(Auth0Client.prototype, "saveToSession") .mockResolvedValue(undefined); // Restore mocking of getTokenSet directly mockGetTokenSet = vi .spyOn(AuthClient.prototype, "getTokenSet") .mockResolvedValue([null, mockRefreshedTokenSet]); // Simulate successful refresh // Remove mocks for discoverAuthorizationServerMetadata and getClientAuth // Remove fetch mock }); it("should throw AccessTokenError if no session exists", async () => { // Override getSession mock for this specific test mockGetSession.mockResolvedValue(null); // Mock request and response objects const mockReq = { headers: new Headers() }; const mockRes = new NextResponse(); await expect(client.getAccessToken(mockReq, mockRes)).rejects.toThrowError(new AccessTokenError(AccessTokenErrorCode.MISSING_SESSION, "The user does not have an active session.")); // Ensure getTokenSet was not called expect(mockGetTokenSet).not.toHaveBeenCalled(); }); it("should throw error from getTokenSet if refresh fails", async () => { const refreshError = new Error("Refresh failed"); // Restore overriding the getTokenSet mock directly mockGetTokenSet.mockResolvedValue([refreshError, null]); // Mock request and response objects const mockReq = { headers: new Headers() }; const mockRes = new NextResponse(); await expect(client.getAccessToken(mockReq, mockRes, { refresh: true })).rejects.toThrowError(refreshError); // Verify save was not called expect(mockSaveToSession).not.toHaveBeenCalled(); }); }); describe("constructor configuration", () => { beforeEach(() => { // Set necessary environment variables process.env[ENV_VARS.DOMAIN] = "test.auth0.com"; process.env[ENV_VARS.CLIENT_ID] = "test_client_id"; process.env[ENV_VARS.CLIENT_SECRET] = "test_client_secret"; process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.test"; process.env[ENV_VARS.SECRET] = "test_secret"; }); it("should pass transactionCookie.maxAge to TransactionStore", () => { const customMaxAge = 1800; // 30 minutes const client = new Auth0Client({ transactionCookie: { maxAge: customMaxAge } }); // Verify that the TransactionStore was created with the correct maxAge // We need to access the private property for testing const transactionStore = client.transactionStore; expect(transactionStore).toBeDefined(); // Check the cookieOptions maxAge - we need to verify it was set correctly const cookieOptions = transactionStore.cookieOptions; expect(cookieOptions.maxAge).toBe(customMaxAge); }); it("should use default maxAge of 3600 when not specified", () => { const client = new Auth0Client(); // Verify that the TransactionStore was created with the default maxAge const transactionStore = client.transactionStore; expect(transactionStore).toBeDefined(); // Check the cookieOptions maxAge const cookieOptions = transactionStore.cookieOptions; expect(cookieOptions.maxAge).toBe(3600); }); it("should pass other transactionCookie options to TransactionStore", () => { const customOptions = { prefix: "__custom_txn_", secure: true, sameSite: "strict", path: "/auth", maxAge: 2700 }; const client = new Auth0Client({ transactionCookie: customOptions }); // Verify that the TransactionStore was created with the correct options const transactionStore = client.transactionStore; expect(transactionStore).toBeDefined(); const cookieOptions = transactionStore.cookieOptions; expect(cookieOptions.maxAge).toBe(customOptions.maxAge); expect(transactionStore.transactionCookiePrefix).toBe(customOptions.prefix); // Note: secure and sameSite are stored in cookieOptions expect(cookieOptions.secure).toBe(customOptions.secure); expect(cookieOptions.sameSite).toBe(customOptions.sameSite); expect(cookieOptions.path).toBe(customOptions.path); }); it("should pass enableParallelTransactions to TransactionStore", () => { const client = new Auth0Client({ enableParallelTransactions: false }); // Verify that the TransactionStore was created with the correct enableParallelTransactions const transactionStore = client.transactionStore; expect(transactionStore).toBeDefined(); const enableParallelTransactions = transactionStore .enableParallelTransactions; expect(enableParallelTransactions).toBe(false); }); it("should default enableParallelTransactions to true when not specified", () => { const client = new Auth0Client(); // Verify that the TransactionStore was created with the default enableParallelTransactions const transactionStore = client.transactionStore; expect(transactionStore).toBeDefined(); const enableParallelTransactions = transactionStore .enableParallelTransactions; expect(enableParallelTransactions).toBe(true); }); }); });