|
1 | | -import { NextResponse } from "next/server.js"; |
| 1 | +import { NextRequest, NextResponse } from "next/server.js"; |
2 | 2 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
3 | 3 |
|
4 | | -import { InvalidConfigurationError } from "../errors/index.js"; |
| 4 | +import { |
| 5 | + DomainResolutionError, |
| 6 | + InvalidConfigurationError |
| 7 | +} from "../errors/index.js"; |
5 | 8 | import { SessionData } from "../types/index.js"; |
6 | 9 | import { Auth0Client } from "./client.js"; |
7 | 10 |
|
@@ -151,6 +154,63 @@ describe("Auth0Client", () => { |
151 | 154 | "tokenRefreshBuffer must be a non-negative number of seconds." |
152 | 155 | ); |
153 | 156 | }); |
| 157 | + |
| 158 | + describe("deferred domain resolution (standalone / runtime-injected env)", () => { |
| 159 | + it("should not throw during construction when AUTH0_DOMAIN is absent and domain is not passed", () => { |
| 160 | + // Simulate a Next.js standalone build where AUTH0_DOMAIN is only injected at runtime. |
| 161 | + // The Auth0Client constructor must not throw — domain validation is deferred to request time. |
| 162 | + delete process.env[ENV_VARS.DOMAIN]; |
| 163 | + process.env[ENV_VARS.CLIENT_ID] = "client_123"; |
| 164 | + process.env[ENV_VARS.CLIENT_SECRET] = "client_secret"; |
| 165 | + process.env[ENV_VARS.APP_BASE_URL] = "https://app.example.com"; |
| 166 | + process.env[ENV_VARS.SECRET] = "secret_value"; |
| 167 | + |
| 168 | + expect(() => new Auth0Client()).not.toThrow(); |
| 169 | + }); |
| 170 | + |
| 171 | + it("should resolve domain at request time when AUTH0_DOMAIN is set after construction", async () => { |
| 172 | + // Domain is absent at construction, but present when the first request is made. |
| 173 | + delete process.env[ENV_VARS.DOMAIN]; |
| 174 | + process.env[ENV_VARS.CLIENT_ID] = "client_123"; |
| 175 | + process.env[ENV_VARS.CLIENT_SECRET] = "client_secret"; |
| 176 | + process.env[ENV_VARS.APP_BASE_URL] = "https://app.example.com"; |
| 177 | + process.env[ENV_VARS.SECRET] = "secret_value"; |
| 178 | + |
| 179 | + const client = new Auth0Client(); |
| 180 | + |
| 181 | + // Now inject the domain as if a container runtime has set it |
| 182 | + process.env[ENV_VARS.DOMAIN] = "runtime.auth0.com"; |
| 183 | + |
| 184 | + // Calling getSession with no active session should not throw an |
| 185 | + // InvalidConfigurationError — the domain is now resolvable. |
| 186 | + // getSession returns null when there is no session; it should NOT throw |
| 187 | + // because domain is now available via the deferred resolver. |
| 188 | + const req = new NextRequest("https://app.example.com/"); |
| 189 | + await expect(client.getSession(req)).resolves.toBeNull(); |
| 190 | + }); |
| 191 | + |
| 192 | + it("should throw InvalidConfigurationError at request time when AUTH0_DOMAIN is still absent", async () => { |
| 193 | + // Both build time and request time are missing AUTH0_DOMAIN — the deferred |
| 194 | + // resolver must throw with a clear message rather than a cryptic internal error. |
| 195 | + delete process.env[ENV_VARS.DOMAIN]; |
| 196 | + process.env[ENV_VARS.CLIENT_ID] = "client_123"; |
| 197 | + process.env[ENV_VARS.CLIENT_SECRET] = "client_secret"; |
| 198 | + process.env[ENV_VARS.APP_BASE_URL] = "https://app.example.com"; |
| 199 | + process.env[ENV_VARS.SECRET] = "secret_value"; |
| 200 | + |
| 201 | + const client = new Auth0Client(); |
| 202 | + |
| 203 | + // AUTH0_DOMAIN remains unset — should throw at request time. |
| 204 | + // The deferred resolver throws InvalidConfigurationError, which the |
| 205 | + // AuthClientProvider wraps in a DomainResolutionError. The original |
| 206 | + // message is accessible via .cause. |
| 207 | + const req = new NextRequest("https://app.example.com/"); |
| 208 | + const err = await client.getSession(req).catch((e) => e); |
| 209 | + expect(err).toBeInstanceOf(DomainResolutionError); |
| 210 | + expect(err.cause).toBeInstanceOf(InvalidConfigurationError); |
| 211 | + expect(err.cause?.message).toContain("Missing: domain"); |
| 212 | + }); |
| 213 | + }); |
154 | 214 | }); |
155 | 215 |
|
156 | 216 | // TODO: Re-implement DPoP handle management if needed |
|
0 commit comments