@nexartis/sentinel-sdk 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +43 -6
  2. package/dist/core/auth-utils.d.ts.map +1 -1
  3. package/dist/core/auth-utils.js +23 -3
  4. package/dist/core/auth-utils.js.map +1 -1
  5. package/dist/core/client.d.ts +4 -0
  6. package/dist/core/client.d.ts.map +1 -1
  7. package/dist/core/client.js +6 -0
  8. package/dist/core/client.js.map +1 -1
  9. package/dist/core/consumer-version-floor.d.ts +56 -0
  10. package/dist/core/consumer-version-floor.d.ts.map +1 -0
  11. package/dist/core/consumer-version-floor.js +233 -0
  12. package/dist/core/consumer-version-floor.js.map +1 -0
  13. package/dist/core/index.d.ts +2 -0
  14. package/dist/core/index.d.ts.map +1 -1
  15. package/dist/core/index.js +1 -0
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/core/jwks.d.ts +15 -1
  18. package/dist/core/jwks.d.ts.map +1 -1
  19. package/dist/core/jwks.js +54 -10
  20. package/dist/core/jwks.js.map +1 -1
  21. package/dist/core/types.d.ts +35 -1
  22. package/dist/core/types.d.ts.map +1 -1
  23. package/dist/core/types.js.map +1 -1
  24. package/dist/sveltekit/cookies.d.ts +10 -0
  25. package/dist/sveltekit/cookies.d.ts.map +1 -1
  26. package/dist/sveltekit/cookies.js +13 -0
  27. package/dist/sveltekit/cookies.js.map +1 -1
  28. package/dist/sveltekit/handle.js +8 -2
  29. package/dist/sveltekit/handle.js.map +1 -1
  30. package/dist/sveltekit/handlers/index.d.ts.map +1 -1
  31. package/dist/sveltekit/handlers/index.js +30 -5
  32. package/dist/sveltekit/handlers/index.js.map +1 -1
  33. package/dist/sveltekit/pages/index.d.ts.map +1 -1
  34. package/dist/sveltekit/pages/index.js +21 -10
  35. package/dist/sveltekit/pages/index.js.map +1 -1
  36. package/package.json +8 -3
@@ -2,7 +2,7 @@ import { SentinelClient } from '../core/client.js';
2
2
  import { isTokenExpired, decodeTokenLocally } from '../core/auth-utils.js';
3
3
  import { createJwksVerifier } from '../core/jwks.js';
4
4
  import { createLogger } from '../core/logger.js';
5
- import { COOKIE_NAMES, COOKIE_OPTIONS } from './cookies.js';
5
+ import { COOKIE_NAMES, COOKIE_OPTIONS, setCommunityAccessCookie } from './cookies.js';
6
6
  /**
7
7
  * DEVELOPMENT-ONLY escape hatch. When set to "1" (or "true") the handle skips
8
8
  * JWKS signature verification and trusts the decoded token payload, which is
@@ -220,7 +220,10 @@ function createUserLocals(decoded) {
220
220
  isAuthenticated: true,
221
221
  id: decoded.sub,
222
222
  email: decoded.email,
223
- roles: decoded.roles || []
223
+ // TRUSTIP-147: roles may be absent on post-cutover tokens — keep a
224
+ // defined array for existing consumers.
225
+ roles: decoded.roles || [],
226
+ platformAdmin: decoded.platform_admin === true
224
227
  };
225
228
  }
226
229
  async function tryRefreshToken(event, config, refreshToken, log, verifier) {
@@ -236,6 +239,9 @@ async function tryRefreshToken(event, config, refreshToken, log, verifier) {
236
239
  if (result.refresh_token) {
237
240
  event.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);
238
241
  }
242
+ if (result.community_access_token) {
243
+ setCommunityAccessCookie(event.cookies, result.community_access_token);
244
+ }
239
245
  // Verify the freshly-issued token's signature before trusting its
240
246
  // payload (same JWKS path as the cookie flow). The cookie is kept —
241
247
  // it was just minted by the backend over TLS — but locals.user is
@@ -1 +1 @@
1
- {"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/sveltekit/handle.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/handle.ts\nimport type { Handle, RequestEvent } from '@sveltejs/kit';\nimport type { SentinelHandleOptions, UserLocals, SentinelConfig, DecodedToken } from '../core/types.js';\nimport { SentinelClient } from '../core/client.js';\nimport { isTokenExpired, decodeTokenLocally } from '../core/auth-utils.js';\nimport { createJwksVerifier, type JwksVerifier } from '../core/jwks.js';\nimport { createLogger, type SentinelLogger } from '../core/logger.js';\nimport { COOKIE_NAMES, COOKIE_OPTIONS } from './cookies.js';\n\n// Node fallback for the dev-only skip flag (mirrors src/sveltekit/utils.ts).\ndeclare const process: any;\n\n/**\n * DEVELOPMENT-ONLY escape hatch. When set to \"1\" (or \"true\") the handle skips\n * JWKS signature verification and trusts the decoded token payload, which is\n * the pre-1.8.0 behaviour and the exact hole the admin-gate audit flagged.\n * Never set this in production: when NODE_ENV=production the flag is REFUSED\n * (construction throws for the programmatic option; per-request the flag is\n * ignored with a loud classified error) and verification always runs.\n */\nconst SKIP_VERIFY_ENV = 'SENTINEL_SKIP_JWKS_VERIFY';\n\nlet skipVerifyWarningEmitted = false;\nfunction warnSkipVerifyOnce(log: SentinelLogger): void {\n\tif (skipVerifyWarningEmitted) return;\n\tskipVerifyWarningEmitted = true;\n\tlog.warn(\n\t\t`${SKIP_VERIFY_ENV} is enabled — JWT signatures are NOT being verified. ` +\n\t\t\t'Access-token payloads are trusted unverified. Development use only.'\n\t);\n}\n\nlet skipVerifyRefusalEmitted = false;\nfunction refuseSkipVerifyInProduction(log: SentinelLogger): void {\n\tif (skipVerifyRefusalEmitted) return;\n\tskipVerifyRefusalEmitted = true;\n\tlog.error(\n\t\t`${SKIP_VERIFY_ENV} refused in production (NODE_ENV=production) — ` +\n\t\t\t'refusing to disable JWKS signature verification. ' +\n\t\t\t'Unset the flag or run with NODE_ENV=development.'\n\t);\n}\n\n/** Test-only: reset the once-per-isolate skip-verify warning/refusal latches. */\nexport function __resetSkipVerifyWarningForTests(): void {\n\tskipVerifyWarningEmitted = false;\n\tskipVerifyRefusalEmitted = false;\n}\n\n/** Locals shape set by the Sentinel auth middleware. */\nexport interface SentinelLocals {\n\tuser: UserLocals | null;\n\taccessToken: string | null;\n}\n\n/**\n * Creates a SvelteKit handle function with Sentinel authentication built-in.\n *\n * Features:\n * - JWKS signature verification of the access token on every request\n * (Ed25519/EdDSA, cached public keys — no per-request API call)\n * - API-validated token refresh when expired or failing verification\n * - Local expiration check (saves API calls)\n * - Protected route redirection\n * - Sets event.locals.user for all routes\n * - Never clears cookies (only explicit logout does)\n */\nexport function createSentinelHandle(options: SentinelHandleOptions = {}): Handle {\n\tconst {\n\t\tprotectedRoutes = [],\n\t\tloginRedirect = '/auth'\n\t} = options;\n\tconst log = options.logger ?? createLogger('sentinel-handle');\n\n\t// Fail fast at construction when the unsafe escape hatch is requested in production.\n\tif (\n\t\toptions.skipJwksVerify === true &&\n\t\ttypeof process !== 'undefined' &&\n\t\tprocess.env?.NODE_ENV === 'production'\n\t) {\n\t\tthrow new Error(\n\t\t\t`${SKIP_VERIFY_ENV}: skipJwksVerify option refused in production ` +\n\t\t\t\t'(NODE_ENV=production) — JWKS signature verification cannot be disabled.'\n\t\t);\n\t}\n\n\treturn async ({ event, resolve }) => {\n\t\t// Initialize locals with typed shape\n\t\tconst locals = event.locals as SentinelLocals;\n\t\tlocals.user = null;\n\t\tlocals.accessToken = null;\n\n\t\t// Get config from environment or options (with request URL fallback)\n\t\tconst config = getConfig(options, event.platform, event.request.url);\n\n\t\tconst skipVerify = isJwksVerifyDisabled(options, event.platform, log);\n\t\tif (skipVerify) warnSkipVerifyOnce(log);\n\t\tconst verifier = skipVerify ? null : createVerifier(options, config, event, log);\n\n\t\tconst accessToken = event.cookies.get(COOKIE_NAMES.ACCESS_TOKEN);\n\t\tconst refreshToken = event.cookies.get(COOKIE_NAMES.REFRESH_TOKEN);\n\n\t\tif (accessToken) {\n\t\t\tif (!isTokenExpired(accessToken)) {\n\t\t\t\tconst decoded = await resolveVerifiedToken(accessToken, verifier, log);\n\t\t\t\tif (decoded) {\n\t\t\t\t\tlocals.user = createUserLocals(decoded);\n\t\t\t\t\tlocals.accessToken = accessToken;\n\t\t\t\t} else {\n\t\t\t\t\tawait tryRefreshToken(event, config, refreshToken, log, verifier);\n\t\t\t\t}\n\t\t\t} else if (refreshToken) {\n\t\t\t\tawait tryRefreshToken(event, config, refreshToken, log, verifier);\n\t\t\t}\n\t\t} else if (refreshToken) {\n\t\t\tawait tryRefreshToken(event, config, refreshToken, log, verifier);\n\t\t}\n\n\t\t// Protected routes check — redirect to login with return URL\n\t\tconst isProtectedRoute = protectedRoutes.some(route =>\n\t\t\tevent.url.pathname.startsWith(route)\n\t\t);\n\n\t\tif (isProtectedRoute && !locals.user) {\n\t\t\tconst returnPath = event.url.pathname + event.url.search;\n\t\t\tconst redirectUrl = `${loginRedirect}?redirect=${encodeURIComponent(returnPath)}`;\n\t\t\treturn new Response('Redirect', {\n\t\t\t\tstatus: 302,\n\t\t\t\theaders: { Location: redirectUrl }\n\t\t\t});\n\t\t}\n\n\t\tconst response = await resolve(event);\n\n\t\t// Add security headers\n\t\tresponse.headers.set('X-Frame-Options', 'DENY');\n\t\tresponse.headers.set('X-Content-Type-Options', 'nosniff');\n\t\tresponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n\n\t\treturn response;\n\t};\n}\n\n/**\n * Verify an access token's signature against the auth server's JWKS and\n * return the decoded payload only when the signature checks out.\n * With `verifier === null` (explicit dev-only opt-out) falls back to the\n * legacy unverified local decode. Never throws.\n */\nasync function resolveVerifiedToken(\n\taccessToken: string,\n\tverifier: JwksVerifier | null,\n\tlog: SentinelLogger\n): Promise<DecodedToken | null> {\n\tif (!verifier) {\n\t\treturn decodeTokenLocally(accessToken);\n\t}\n\tconst outcome = await verifier.verify(accessToken);\n\tif (outcome.status === 'valid') {\n\t\treturn outcome.payload;\n\t}\n\tlog.warn('Access token rejected by signature verification', {\n\t\tstatus: outcome.status,\n\t\treason: outcome.reason\n\t});\n\treturn null;\n}\n\nfunction isProductionEnvironment(platform: Readonly<App.Platform> | undefined): boolean {\n\tconst platformEnv = (platform as Record<string, any>)?.env as Record<string, string> | undefined;\n\tconst fromPlatform = platformEnv?.NODE_ENV;\n\tconst fromProcess = typeof process !== 'undefined' ? process.env?.NODE_ENV : undefined;\n\treturn (fromPlatform ?? fromProcess) === 'production';\n}\n\nfunction isJwksVerifyDisabled(\n\toptions: SentinelHandleOptions,\n\tplatform: Readonly<App.Platform> | undefined,\n\tlog: SentinelLogger\n): boolean {\n\tif (options.skipJwksVerify === true) {\n\t\tif (isProductionEnvironment(platform)) {\n\t\t\trefuseSkipVerifyInProduction(log);\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\tconst platformEnv = (platform as Record<string, any>)?.env as Record<string, string> | undefined;\n\tconst fromPlatform = platformEnv?.[SKIP_VERIFY_ENV];\n\tconst fromProcess = typeof process !== 'undefined' ? process.env?.[SKIP_VERIFY_ENV] : undefined;\n\tconst value = fromPlatform ?? fromProcess;\n\tconst requested = value === '1' || value === 'true';\n\tif (requested && isProductionEnvironment(platform)) {\n\t\trefuseSkipVerifyInProduction(log);\n\t\treturn false;\n\t}\n\treturn requested;\n}\n\nfunction createVerifier(\n\toptions: SentinelHandleOptions,\n\tconfig: SentinelConfig,\n\tevent: RequestEvent,\n\tlog: SentinelLogger\n): JwksVerifier {\n\tconst platformEnv = (event.platform as Record<string, any>)?.env as Record<string, string> | undefined;\n\tconst jwksUrl = options.jwksUrl || platformEnv?.SENTINEL_JWKS_URL || deriveJwksUrl(config.apiUrl);\n\treturn createJwksVerifier({\n\t\tjwksUrl,\n\t\tfetchFn: event.fetch,\n\t\tcacheTtlMs: options.jwksCacheTtlMs,\n\t\tlogger: log\n\t});\n}\n\n/**\n * Derive the JWKS URL from the Sentinel API URL. getConfig normalises apiUrl\n * to `<origin>/api`, so stripping the `/api` suffix yields the origin that\n * serves `/.well-known/jwks.json`.\n */\nfunction deriveJwksUrl(apiUrl: string): string {\n\tconst origin = apiUrl.endsWith('/api') ? apiUrl.slice(0, -'/api'.length) : apiUrl;\n\tlet trimmed = origin;\n\twhile (trimmed.endsWith('/')) trimmed = trimmed.slice(0, -1);\n\treturn `${trimmed}/.well-known/jwks.json`;\n}\n\nfunction getConfig(options: SentinelHandleOptions, platform: Readonly<App.Platform> | undefined, requestUrl?: string): SentinelConfig {\n\tlet apiUrl = options.apiUrl || (platform as Record<string, any>)?.env?.SENTINEL_API_URL || '';\n\tlet baseUrl = options.baseUrl || (platform as Record<string, any>)?.env?.VITE_BASE_URL || '';\n\n\tif (!apiUrl) {\n\t\tthrow new Error('SENTINEL_API_URL must be configured');\n\t}\n\n\t// Normalize: strip trailing slashes, then append /api if not already present\n\twhile (apiUrl.endsWith('/')) apiUrl = apiUrl.slice(0, -1);\n\tif (!apiUrl.endsWith('/api')) {\n\t\tapiUrl = apiUrl + '/api';\n\t}\n\n\t// ARCH-003 fix: Derive baseUrl from request URL when not configured.\n\t// Workers for Platforms deployments don't know their URL at build time.\n\tif (!baseUrl && requestUrl) {\n\t\ttry {\n\t\t\tconst url = new URL(requestUrl);\n\t\t\tconst isLoopback = url.hostname === 'localhost'\n\t\t\t\t|| url.hostname === '127.0.0.1'\n\t\t\t\t|| url.hostname === '0.0.0.0'\n\t\t\t\t|| url.hostname === '::1';\n\n\t\t\tlet protocol = url.protocol;\n\t\t\tlet forcedHttps = false;\n\t\t\tif (!isLoopback && protocol !== 'https:') {\n\t\t\t\tprotocol = 'https:';\n\t\t\t\tforcedHttps = true;\n\t\t\t}\n\n\t\t\t// Build URL with hostname (not host) to avoid carrying\n\t\t\t// default-for-original-scheme ports across a protocol switch.\n\t\t\t// Drop port entirely when forcing HTTPS (Workers serve on standard ports).\n\t\t\tconst host = url.hostname.includes(':') ? `[${url.hostname}]` : url.hostname;\n\t\t\tconst portSuffix = (!forcedHttps && url.port) ? `:${url.port}` : '';\n\t\t\tbaseUrl = `${protocol}//${host}${portSuffix}`;\n\t\t} catch {\n\t\t\t// If URL parsing fails, baseUrl stays empty\n\t\t}\n\t}\n\n\tif (!baseUrl) {\n\t\tthrow new Error('VITE_BASE_URL must be configured (or provide a valid request URL for runtime detection)');\n\t}\n\n\treturn { apiUrl, baseUrl };\n}\n\nfunction createUserLocals(decoded: DecodedToken): UserLocals {\n\treturn {\n\t\tisAuthenticated: true,\n\t\tid: decoded.sub,\n\t\temail: decoded.email,\n\t\troles: decoded.roles || []\n\t};\n}\n\nasync function tryRefreshToken(event: RequestEvent, config: SentinelConfig, refreshToken: string | undefined, log: SentinelLogger, verifier: JwksVerifier | null): Promise<void> {\n\tif (!refreshToken) return;\n\n\ttry {\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.refreshToken(refreshToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token) {\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.ACCESS_TOKEN);\n\n\t\t\t\t// Handle refresh token rotation\n\t\t\t\tif (result.refresh_token) {\n\t\t\t\t\tevent.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);\n\t\t\t\t}\n\n\t\t\t\t// Verify the freshly-issued token's signature before trusting its\n\t\t\t\t// payload (same JWKS path as the cookie flow). The cookie is kept —\n\t\t\t\t// it was just minted by the backend over TLS — but locals.user is\n\t\t\t\t// only set when verification passes; the next request retries.\n\t\t\t\tconst decoded = await resolveVerifiedToken(result.access_token, verifier, log);\n\t\t\t\tif (decoded) {\n\t\t\t\t\tconst locals = event.locals as SentinelLocals;\n\t\t\t\t\tlocals.user = createUserLocals(decoded);\n\t\t\t\t\tlocals.accessToken = result.access_token;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.warn('Token refresh failed - continuing without user');\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Token refresh error', { error: String(error) });\n\t}\n}\n\n"]}
1
+ {"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/sveltekit/handle.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/handle.ts\nimport type { Handle, RequestEvent } from '@sveltejs/kit';\nimport type { SentinelHandleOptions, UserLocals, SentinelConfig, DecodedToken } from '../core/types.js';\nimport { SentinelClient } from '../core/client.js';\nimport { isTokenExpired, decodeTokenLocally } from '../core/auth-utils.js';\nimport { createJwksVerifier, type JwksVerifier } from '../core/jwks.js';\nimport { createLogger, type SentinelLogger } from '../core/logger.js';\nimport { COOKIE_NAMES, COOKIE_OPTIONS, setCommunityAccessCookie } from './cookies.js';\n\n// Node fallback for the dev-only skip flag (mirrors src/sveltekit/utils.ts).\ndeclare const process: any;\n\n/**\n * DEVELOPMENT-ONLY escape hatch. When set to \"1\" (or \"true\") the handle skips\n * JWKS signature verification and trusts the decoded token payload, which is\n * the pre-1.8.0 behaviour and the exact hole the admin-gate audit flagged.\n * Never set this in production: when NODE_ENV=production the flag is REFUSED\n * (construction throws for the programmatic option; per-request the flag is\n * ignored with a loud classified error) and verification always runs.\n */\nconst SKIP_VERIFY_ENV = 'SENTINEL_SKIP_JWKS_VERIFY';\n\nlet skipVerifyWarningEmitted = false;\nfunction warnSkipVerifyOnce(log: SentinelLogger): void {\n\tif (skipVerifyWarningEmitted) return;\n\tskipVerifyWarningEmitted = true;\n\tlog.warn(\n\t\t`${SKIP_VERIFY_ENV} is enabled — JWT signatures are NOT being verified. ` +\n\t\t\t'Access-token payloads are trusted unverified. Development use only.'\n\t);\n}\n\nlet skipVerifyRefusalEmitted = false;\nfunction refuseSkipVerifyInProduction(log: SentinelLogger): void {\n\tif (skipVerifyRefusalEmitted) return;\n\tskipVerifyRefusalEmitted = true;\n\tlog.error(\n\t\t`${SKIP_VERIFY_ENV} refused in production (NODE_ENV=production) — ` +\n\t\t\t'refusing to disable JWKS signature verification. ' +\n\t\t\t'Unset the flag or run with NODE_ENV=development.'\n\t);\n}\n\n/** Test-only: reset the once-per-isolate skip-verify warning/refusal latches. */\nexport function __resetSkipVerifyWarningForTests(): void {\n\tskipVerifyWarningEmitted = false;\n\tskipVerifyRefusalEmitted = false;\n}\n\n/** Locals shape set by the Sentinel auth middleware. */\nexport interface SentinelLocals {\n\tuser: UserLocals | null;\n\taccessToken: string | null;\n}\n\n/**\n * Creates a SvelteKit handle function with Sentinel authentication built-in.\n *\n * Features:\n * - JWKS signature verification of the access token on every request\n * (Ed25519/EdDSA, cached public keys — no per-request API call)\n * - API-validated token refresh when expired or failing verification\n * - Local expiration check (saves API calls)\n * - Protected route redirection\n * - Sets event.locals.user for all routes\n * - Never clears cookies (only explicit logout does)\n */\nexport function createSentinelHandle(options: SentinelHandleOptions = {}): Handle {\n\tconst {\n\t\tprotectedRoutes = [],\n\t\tloginRedirect = '/auth'\n\t} = options;\n\tconst log = options.logger ?? createLogger('sentinel-handle');\n\n\t// Fail fast at construction when the unsafe escape hatch is requested in production.\n\tif (\n\t\toptions.skipJwksVerify === true &&\n\t\ttypeof process !== 'undefined' &&\n\t\tprocess.env?.NODE_ENV === 'production'\n\t) {\n\t\tthrow new Error(\n\t\t\t`${SKIP_VERIFY_ENV}: skipJwksVerify option refused in production ` +\n\t\t\t\t'(NODE_ENV=production) — JWKS signature verification cannot be disabled.'\n\t\t);\n\t}\n\n\treturn async ({ event, resolve }) => {\n\t\t// Initialize locals with typed shape\n\t\tconst locals = event.locals as SentinelLocals;\n\t\tlocals.user = null;\n\t\tlocals.accessToken = null;\n\n\t\t// Get config from environment or options (with request URL fallback)\n\t\tconst config = getConfig(options, event.platform, event.request.url);\n\n\t\tconst skipVerify = isJwksVerifyDisabled(options, event.platform, log);\n\t\tif (skipVerify) warnSkipVerifyOnce(log);\n\t\tconst verifier = skipVerify ? null : createVerifier(options, config, event, log);\n\n\t\tconst accessToken = event.cookies.get(COOKIE_NAMES.ACCESS_TOKEN);\n\t\tconst refreshToken = event.cookies.get(COOKIE_NAMES.REFRESH_TOKEN);\n\n\t\tif (accessToken) {\n\t\t\tif (!isTokenExpired(accessToken)) {\n\t\t\t\tconst decoded = await resolveVerifiedToken(accessToken, verifier, log);\n\t\t\t\tif (decoded) {\n\t\t\t\t\tlocals.user = createUserLocals(decoded);\n\t\t\t\t\tlocals.accessToken = accessToken;\n\t\t\t\t} else {\n\t\t\t\t\tawait tryRefreshToken(event, config, refreshToken, log, verifier);\n\t\t\t\t}\n\t\t\t} else if (refreshToken) {\n\t\t\t\tawait tryRefreshToken(event, config, refreshToken, log, verifier);\n\t\t\t}\n\t\t} else if (refreshToken) {\n\t\t\tawait tryRefreshToken(event, config, refreshToken, log, verifier);\n\t\t}\n\n\t\t// Protected routes check — redirect to login with return URL\n\t\tconst isProtectedRoute = protectedRoutes.some(route =>\n\t\t\tevent.url.pathname.startsWith(route)\n\t\t);\n\n\t\tif (isProtectedRoute && !locals.user) {\n\t\t\tconst returnPath = event.url.pathname + event.url.search;\n\t\t\tconst redirectUrl = `${loginRedirect}?redirect=${encodeURIComponent(returnPath)}`;\n\t\t\treturn new Response('Redirect', {\n\t\t\t\tstatus: 302,\n\t\t\t\theaders: { Location: redirectUrl }\n\t\t\t});\n\t\t}\n\n\t\tconst response = await resolve(event);\n\n\t\t// Add security headers\n\t\tresponse.headers.set('X-Frame-Options', 'DENY');\n\t\tresponse.headers.set('X-Content-Type-Options', 'nosniff');\n\t\tresponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n\n\t\treturn response;\n\t};\n}\n\n/**\n * Verify an access token's signature against the auth server's JWKS and\n * return the decoded payload only when the signature checks out.\n * With `verifier === null` (explicit dev-only opt-out) falls back to the\n * legacy unverified local decode. Never throws.\n */\nasync function resolveVerifiedToken(\n\taccessToken: string,\n\tverifier: JwksVerifier | null,\n\tlog: SentinelLogger\n): Promise<DecodedToken | null> {\n\tif (!verifier) {\n\t\treturn decodeTokenLocally(accessToken);\n\t}\n\tconst outcome = await verifier.verify(accessToken);\n\tif (outcome.status === 'valid') {\n\t\treturn outcome.payload;\n\t}\n\tlog.warn('Access token rejected by signature verification', {\n\t\tstatus: outcome.status,\n\t\treason: outcome.reason\n\t});\n\treturn null;\n}\n\nfunction isProductionEnvironment(platform: Readonly<App.Platform> | undefined): boolean {\n\tconst platformEnv = (platform as Record<string, any>)?.env as Record<string, string> | undefined;\n\tconst fromPlatform = platformEnv?.NODE_ENV;\n\tconst fromProcess = typeof process !== 'undefined' ? process.env?.NODE_ENV : undefined;\n\treturn (fromPlatform ?? fromProcess) === 'production';\n}\n\nfunction isJwksVerifyDisabled(\n\toptions: SentinelHandleOptions,\n\tplatform: Readonly<App.Platform> | undefined,\n\tlog: SentinelLogger\n): boolean {\n\tif (options.skipJwksVerify === true) {\n\t\tif (isProductionEnvironment(platform)) {\n\t\t\trefuseSkipVerifyInProduction(log);\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\tconst platformEnv = (platform as Record<string, any>)?.env as Record<string, string> | undefined;\n\tconst fromPlatform = platformEnv?.[SKIP_VERIFY_ENV];\n\tconst fromProcess = typeof process !== 'undefined' ? process.env?.[SKIP_VERIFY_ENV] : undefined;\n\tconst value = fromPlatform ?? fromProcess;\n\tconst requested = value === '1' || value === 'true';\n\tif (requested && isProductionEnvironment(platform)) {\n\t\trefuseSkipVerifyInProduction(log);\n\t\treturn false;\n\t}\n\treturn requested;\n}\n\nfunction createVerifier(\n\toptions: SentinelHandleOptions,\n\tconfig: SentinelConfig,\n\tevent: RequestEvent,\n\tlog: SentinelLogger\n): JwksVerifier {\n\tconst platformEnv = (event.platform as Record<string, any>)?.env as Record<string, string> | undefined;\n\tconst jwksUrl = options.jwksUrl || platformEnv?.SENTINEL_JWKS_URL || deriveJwksUrl(config.apiUrl);\n\treturn createJwksVerifier({\n\t\tjwksUrl,\n\t\tfetchFn: event.fetch,\n\t\tcacheTtlMs: options.jwksCacheTtlMs,\n\t\tlogger: log\n\t});\n}\n\n/**\n * Derive the JWKS URL from the Sentinel API URL. getConfig normalises apiUrl\n * to `<origin>/api`, so stripping the `/api` suffix yields the origin that\n * serves `/.well-known/jwks.json`.\n */\nfunction deriveJwksUrl(apiUrl: string): string {\n\tconst origin = apiUrl.endsWith('/api') ? apiUrl.slice(0, -'/api'.length) : apiUrl;\n\tlet trimmed = origin;\n\twhile (trimmed.endsWith('/')) trimmed = trimmed.slice(0, -1);\n\treturn `${trimmed}/.well-known/jwks.json`;\n}\n\nfunction getConfig(options: SentinelHandleOptions, platform: Readonly<App.Platform> | undefined, requestUrl?: string): SentinelConfig {\n\tlet apiUrl = options.apiUrl || (platform as Record<string, any>)?.env?.SENTINEL_API_URL || '';\n\tlet baseUrl = options.baseUrl || (platform as Record<string, any>)?.env?.VITE_BASE_URL || '';\n\n\tif (!apiUrl) {\n\t\tthrow new Error('SENTINEL_API_URL must be configured');\n\t}\n\n\t// Normalize: strip trailing slashes, then append /api if not already present\n\twhile (apiUrl.endsWith('/')) apiUrl = apiUrl.slice(0, -1);\n\tif (!apiUrl.endsWith('/api')) {\n\t\tapiUrl = apiUrl + '/api';\n\t}\n\n\t// ARCH-003 fix: Derive baseUrl from request URL when not configured.\n\t// Workers for Platforms deployments don't know their URL at build time.\n\tif (!baseUrl && requestUrl) {\n\t\ttry {\n\t\t\tconst url = new URL(requestUrl);\n\t\t\tconst isLoopback = url.hostname === 'localhost'\n\t\t\t\t|| url.hostname === '127.0.0.1'\n\t\t\t\t|| url.hostname === '0.0.0.0'\n\t\t\t\t|| url.hostname === '::1';\n\n\t\t\tlet protocol = url.protocol;\n\t\t\tlet forcedHttps = false;\n\t\t\tif (!isLoopback && protocol !== 'https:') {\n\t\t\t\tprotocol = 'https:';\n\t\t\t\tforcedHttps = true;\n\t\t\t}\n\n\t\t\t// Build URL with hostname (not host) to avoid carrying\n\t\t\t// default-for-original-scheme ports across a protocol switch.\n\t\t\t// Drop port entirely when forcing HTTPS (Workers serve on standard ports).\n\t\t\tconst host = url.hostname.includes(':') ? `[${url.hostname}]` : url.hostname;\n\t\t\tconst portSuffix = (!forcedHttps && url.port) ? `:${url.port}` : '';\n\t\t\tbaseUrl = `${protocol}//${host}${portSuffix}`;\n\t\t} catch {\n\t\t\t// If URL parsing fails, baseUrl stays empty\n\t\t}\n\t}\n\n\tif (!baseUrl) {\n\t\tthrow new Error('VITE_BASE_URL must be configured (or provide a valid request URL for runtime detection)');\n\t}\n\n\treturn { apiUrl, baseUrl };\n}\n\nfunction createUserLocals(decoded: DecodedToken): UserLocals {\n\treturn {\n\t\tisAuthenticated: true,\n\t\tid: decoded.sub,\n\t\temail: decoded.email,\n\t\t// TRUSTIP-147: roles may be absent on post-cutover tokens — keep a\n\t\t// defined array for existing consumers.\n\t\troles: decoded.roles || [],\n\t\tplatformAdmin: decoded.platform_admin === true\n\t};\n}\n\nasync function tryRefreshToken(event: RequestEvent, config: SentinelConfig, refreshToken: string | undefined, log: SentinelLogger, verifier: JwksVerifier | null): Promise<void> {\n\tif (!refreshToken) return;\n\n\ttry {\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.refreshToken(refreshToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token) {\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.ACCESS_TOKEN);\n\n\t\t\t\t// Handle refresh token rotation\n\t\t\t\tif (result.refresh_token) {\n\t\t\t\t\tevent.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);\n\t\t\t\t}\n\t\t\t\tif (result.community_access_token) {\n\t\t\t\t\tsetCommunityAccessCookie(event.cookies, result.community_access_token);\n\t\t\t\t}\n\n\t\t\t\t// Verify the freshly-issued token's signature before trusting its\n\t\t\t\t// payload (same JWKS path as the cookie flow). The cookie is kept —\n\t\t\t\t// it was just minted by the backend over TLS — but locals.user is\n\t\t\t\t// only set when verification passes; the next request retries.\n\t\t\t\tconst decoded = await resolveVerifiedToken(result.access_token, verifier, log);\n\t\t\t\tif (decoded) {\n\t\t\t\t\tconst locals = event.locals as SentinelLocals;\n\t\t\t\t\tlocals.user = createUserLocals(decoded);\n\t\t\t\t\tlocals.accessToken = result.access_token;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlog.warn('Token refresh failed - continuing without user');\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Token refresh error', { error: String(error) });\n\t}\n}\n\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,eAAe,CAAC;AAGlE,OAAO,KAAK,EACX,kBAAkB,EAGlB,MAAM,qBAAqB,CAAC;AA0lB7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CA8ClH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,eAAe,CAAC;AAGlE,OAAO,KAAK,EACX,kBAAkB,EAGlB,MAAM,qBAAqB,CAAC;AA+nB7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CA8ClH"}
@@ -4,7 +4,7 @@ import { SentinelClient } from '../../core/client.js';
4
4
  import { SentinelApiError } from '../../core/errors.js';
5
5
  import { decodeTokenLocally } from '../../core/auth-utils.js';
6
6
  import { createLogger } from '../../core/logger.js';
7
- import { setSessionCookies, clearAuthCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';
7
+ import { setSessionCookies, setCommunityAccessCookie, clearAuthCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';
8
8
  import { getConfigFromPlatform, clearLoginCookies } from '../utils.js';
9
9
  const DEFAULT_LOGIN_COOKIE_TTL_SECONDS = 600; // 10 minutes
10
10
  const DEFAULT_LOGIN_REDIRECT = '/auth';
@@ -46,7 +46,10 @@ function userLocalsFromAccessToken(accessToken) {
46
46
  isAuthenticated: true,
47
47
  id: decoded.sub,
48
48
  email: decoded.email,
49
- roles: decoded.roles || []
49
+ // TRUSTIP-147: roles may be absent on post-cutover tokens — keep a
50
+ // defined array for existing consumers.
51
+ roles: decoded.roles || [],
52
+ platformAdmin: decoded.platform_admin === true
50
53
  };
51
54
  }
52
55
  function buildLoginCookieOptions(ttlSeconds) {
@@ -76,7 +79,7 @@ async function handleStartLogin(event, resolved) {
76
79
  const { loginCookieOptions, log, opts } = resolved;
77
80
  try {
78
81
  const body = await event.request.json();
79
- const { email, rememberMe, redirectTo, redirectContext } = body;
82
+ const { email, rememberMe, redirectTo, redirectContext, audience, communityId } = body;
80
83
  // Built-in basic email validation
81
84
  const emailError = validateEmail(email);
82
85
  if (emailError) {
@@ -88,7 +91,11 @@ async function handleStartLogin(event, resolved) {
88
91
  const rejection = await opts.onBeforeLogin(event, {
89
92
  email: trimmedEmail,
90
93
  rememberMe: !!rememberMe,
91
- redirectTo
94
+ redirectTo,
95
+ audience: audience === 'site_owner' || audience === 'fan' || audience === 'admin'
96
+ ? audience
97
+ : undefined,
98
+ communityId: typeof communityId === 'string' ? communityId : undefined
92
99
  });
93
100
  if (rejection) {
94
101
  // 1.5.0: if the rejection carries a safe relative `redirect`, 303
@@ -121,6 +128,12 @@ async function handleStartLogin(event, resolved) {
121
128
  if (typeof rememberMe === 'boolean') {
122
129
  startLoginOpts.rememberMe = rememberMe;
123
130
  }
131
+ if (audience === 'site_owner' || audience === 'fan' || audience === 'admin') {
132
+ startLoginOpts.audience = audience;
133
+ }
134
+ if (typeof communityId === 'string' && communityId.trim()) {
135
+ startLoginOpts.communityId = communityId.trim();
136
+ }
124
137
  const result = await sentinel.startLogin(trimmedEmail, startLoginOpts);
125
138
  // cookiesSet requires both a success signal AND valid tokens to prevent
126
139
  // setting undefined/null cookie values if the API response is malformed.
@@ -321,13 +334,19 @@ async function handleClaimPost(event, resolved) {
321
334
  if ((result.code === 0 || result.success) && result.access_token && result.refresh_token) {
322
335
  const postAuthRedirect = await finalizePostClaimRedirect(event, resolved, result.access_token, rememberMe);
323
336
  setSessionCookies(event.cookies, result.access_token, result.refresh_token, rememberMe);
337
+ if (result.community_access_token) {
338
+ setCommunityAccessCookie(event.cookies, result.community_access_token);
339
+ }
324
340
  clearLoginCookies(event.cookies);
325
341
  // onAfterClaim hook — user creation, profile sync, analytics
326
342
  if (opts.onAfterClaim) {
327
343
  await opts.onAfterClaim(event, {
328
344
  accessToken: result.access_token,
329
345
  refreshToken: result.refresh_token,
330
- redirectTo: postAuthRedirect
346
+ redirectTo: postAuthRedirect,
347
+ communityAccessToken: result.community_access_token,
348
+ communityId: result.community_id,
349
+ communityScope: result.community_scope
331
350
  });
332
351
  }
333
352
  return json({ success: true, redirectTo: postAuthRedirect });
@@ -361,11 +380,17 @@ async function handleClaimGet(event, resolved) {
361
380
  if ((result.code === 0 || result.success) && result.access_token && result.refresh_token) {
362
381
  const postAuthRedirect = await finalizePostClaimRedirect(event, resolved, result.access_token, rememberMe);
363
382
  setSessionCookies(event.cookies, result.access_token, result.refresh_token, rememberMe);
383
+ if (result.community_access_token) {
384
+ setCommunityAccessCookie(event.cookies, result.community_access_token);
385
+ }
364
386
  clearLoginCookies(event.cookies);
365
387
  if (opts.onAfterClaim) {
366
388
  await opts.onAfterClaim(event, {
367
389
  accessToken: result.access_token,
368
390
  refreshToken: result.refresh_token,
391
+ communityAccessToken: result.community_access_token,
392
+ communityId: result.community_id,
393
+ communityScope: result.community_scope,
369
394
  redirectTo: postAuthRedirect
370
395
  });
371
396
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/handlers/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/handlers/index.ts\nimport { json, redirect } from '@sveltejs/kit';\nimport type { RequestHandler, RequestEvent } from '@sveltejs/kit';\nimport { SentinelClient } from '../../core/client.js';\nimport { SentinelApiError } from '../../core/errors.js';\nimport type {\n\tAuthHandlerOptions,\n\tDecodedToken,\n\tUserLocals\n} from '../../core/types.js';\nimport { decodeTokenLocally } from '../../core/auth-utils.js';\nimport { createLogger, type SentinelLogger } from '../../core/logger.js';\nimport { setSessionCookies, clearAuthCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';\nimport { getConfigFromPlatform, clearLoginCookies } from '../utils.js';\n\nconst DEFAULT_LOGIN_COOKIE_TTL_SECONDS = 600; // 10 minutes\nconst DEFAULT_LOGIN_REDIRECT = '/auth';\nconst MAX_EMAIL_LENGTH = 254; // RFC 5321\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Resolved options with defaults applied. Computed once in createAuthHandlers. */\ninterface ResolvedOptions {\n\tloginCookieOptions: ReturnType<typeof buildLoginCookieOptions>;\n\tloginRedirect: string;\n\tredirectCookie: boolean;\n\tlog: SentinelLogger;\n\topts: AuthHandlerOptions;\n}\n\n/**\n * True if `path` is a safe relative path: starts with `/`, is not `//x` or\n * `/\\x` (protocol-relative), carries no scheme prefix (http:, javascript:, data:),\n * and contains no whitespace or control characters (defence-in-depth against\n * header-injection via CR/LF/tab smuggled through a `Location` header).\n */\nfunction isSafeRelativePath(path: string): boolean {\n\tif (typeof path !== 'string' || path.length === 0) return false;\n\tif (!path.startsWith('/')) return false;\n\tif (/^[/\\\\]{2}/.test(path)) return false;\n\tif (/^\\/[a-z][a-z0-9+.\\-]*:/i.test(path)) return false;\n\t// Reject any ASCII control char (incl. CR/LF/TAB) or whitespace.\n\tif (/[\\s\\u0000-\\u001f\\u007f]/.test(path)) return false;\n\treturn true;\n}\n\n/** Sanitise a redirect path to prevent open-redirect via env misconfiguration. */\nfunction safeRedirectPath(raw: string | undefined): string {\n\tconst path = (raw || '/').trim();\n\tif (!isSafeRelativePath(path)) return '/';\n\treturn path;\n}\n\n/** Build UserLocals from a freshly-issued access token, or null if the token won't decode. */\nfunction userLocalsFromAccessToken(accessToken: string): UserLocals | null {\n\tconst decoded: DecodedToken | null = decodeTokenLocally(accessToken);\n\tif (!decoded) return null;\n\treturn {\n\t\tisAuthenticated: true,\n\t\tid: decoded.sub,\n\t\temail: decoded.email,\n\t\troles: decoded.roles || []\n\t};\n}\n\nfunction buildLoginCookieOptions(ttlSeconds: number) {\n\treturn {\n\t\tpath: '/',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: ttlSeconds\n\t};\n}\n\n/** Validate email: present, non-empty string, max 254 chars, basic format. */\nfunction validateEmail(email: unknown): string | null {\n\tif (!email || typeof email !== 'string') return 'Email is required';\n\tconst trimmed = email.trim();\n\tif (!trimmed) return 'Email is required';\n\tif (trimmed.length > MAX_EMAIL_LENGTH) return 'Email exceeds maximum length';\n\tif (!EMAIL_REGEX.test(trimmed)) return 'Invalid email format';\n\treturn null; // valid\n}\n\n// Start Login Handler\nasync function handleStartLogin(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { loginCookieOptions, log, opts } = resolved;\n\ttry {\n\t\tconst body = await event.request.json();\n\t\tconst { email, rememberMe, redirectTo, redirectContext } = body;\n\n\t\t// Built-in basic email validation\n\t\tconst emailError = validateEmail(email);\n\t\tif (emailError) {\n\t\t\treturn json({ success: false, error: emailError }, { status: 400 });\n\t\t}\n\t\tconst trimmedEmail = (email as string).trim();\n\n\t\t// onBeforeLogin hook — custom validation, rate limiting, etc.\n\t\tif (opts.onBeforeLogin) {\n\t\t\tconst rejection = await opts.onBeforeLogin(event, {\n\t\t\t\temail: trimmedEmail,\n\t\t\t\trememberMe: !!rememberMe,\n\t\t\t\tredirectTo\n\t\t\t});\n\t\t\tif (rejection) {\n\t\t\t\t// 1.5.0: if the rejection carries a safe relative `redirect`, 303\n\t\t\t\t// to that branded destination instead of returning a JSON error.\n\t\t\t\t// 303 (See Other) converts the POST into a GET on the client.\n\t\t\t\tif (rejection.redirect && isSafeRelativePath(rejection.redirect)) {\n\t\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\t\treturn new Response(null, {\n\t\t\t\t\t\tstatus: 303,\n\t\t\t\t\t\theaders: { Location: rejection.redirect }\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (rejection.redirect) {\n\t\t\t\t\tlog.warn('onBeforeLogin returned unsafe redirect — falling back to JSON', {\n\t\t\t\t\t\tredirect: rejection.redirect\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// Clear stale login cookies on JSON reject so a rejected login never\n\t\t\t\t// leaves a half-open transaction behind. Mirrors the 303 path above.\n\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\treturn json({ success: false, error: rejection.error }, { status: rejection.status ?? 400 });\n\t\t\t}\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst startLoginOpts: { redirectContext?: string; rememberMe?: boolean } = {};\n\t\tif (typeof redirectContext === 'string') {\n\t\t\tstartLoginOpts.redirectContext = redirectContext;\n\t\t}\n\t\tif (typeof rememberMe === 'boolean') {\n\t\t\tstartLoginOpts.rememberMe = rememberMe;\n\t\t}\n\t\tconst result = await sentinel.startLogin(trimmedEmail, startLoginOpts);\n\n\t\t// cookiesSet requires both a success signal AND valid tokens to prevent\n\t\t// setting undefined/null cookie values if the API response is malformed.\n\t\tconst cookiesSet = Boolean(\n\t\t\t(result.code === 0 || result.success) &&\n\t\t\tresult.transactionId &&\n\t\t\tresult.clientToken\n\t\t);\n\n\t\tif (cookiesSet) {\n\t\t\tevent.cookies.set('login_transaction_id', result.transactionId!, loginCookieOptions);\n\t\t\tevent.cookies.set('login_client_token', result.clientToken!, loginCookieOptions);\n\t\t\tevent.cookies.set('login_remember_me', rememberMe ? 'true' : 'false', loginCookieOptions);\n\n\t\t\t// Built-in redirectTo cookie — stores where to send user after login\n\t\t\tif (resolved.redirectCookie && redirectTo) {\n\t\t\t\tconst safeTarget = safeRedirectPath(redirectTo);\n\t\t\t\tif (safeTarget !== '/') {\n\t\t\t\t\tevent.cookies.set('login_redirect_to', safeTarget, loginCookieOptions);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// onAfterLogin hook — audit logging, analytics, etc.\n\t\t\tif (opts.onAfterLogin) {\n\t\t\t\tawait opts.onAfterLogin(event, {\n\t\t\t\t\temail: trimmedEmail,\n\t\t\t\t\ttransactionId: result.transactionId!\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tlog.error('Start login issue', { code: result.code, error: result.error });\n\t\t\tclearLoginCookies(event.cookies);\n\t\t}\n\n\t\t// Always return same message to prevent user enumeration.\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is registered, you will receive a magic link shortly.',\n\t\t\tpollReady: cookiesSet\n\t\t});\n\t} catch (error) {\n\t\tlog.error('Start login error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'start-login', error });\n\t\tclearLoginCookies(event.cookies);\n\n\t\t// Fail loud on configuration / client errors so misconfigured origins or\n\t\t// invalid requests are surfaced immediately instead of masking as success.\n\t\tif (error instanceof SentinelApiError && error.status === 400) {\n\t\t\tconst body = error.body as Record<string, any> | undefined;\n\t\t\tif (body && typeof body === 'object' && (body.code === 4003 || body.code === 'EMAIL_ENDPOINT_UNAPPROVED')) {\n\t\t\t\treturn json(\n\t\t\t\t\t{\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: body.message || 'Email endpoint host is not approved'\n\t\t\t\t\t},\n\t\t\t\t\t{ status: 400 }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is registered, you will receive a magic link shortly.',\n\t\t\tpollReady: false\n\t\t});\n\t}\n}\n\nfunction extractClientErrorMessage(error: SentinelApiError, defaultMsg: string): string {\n\tconst body = error.body;\n\tif (typeof body === 'string' && body.trim().length > 0) {\n\t\treturn body;\n\t}\n\tif (typeof body === 'object' && body !== null) {\n\t\tconst rec = body as Record<string, any>;\n\t\tif (typeof rec.message === 'string' && rec.message.length > 0) return rec.message;\n\t\tif (typeof rec.error === 'string' && rec.error.length > 0) return rec.error;\n\t}\n\treturn defaultMsg;\n}\n\n// Poll Handler\nasync function handlePoll(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { loginCookieOptions, log, opts } = resolved;\n\ttry {\n\t\tconst transactionId = event.cookies.get('login_transaction_id');\n\t\tconst clientToken = event.cookies.get('login_client_token');\n\n\t\tif (!transactionId) {\n\t\t\treturn json({ status: 'expired' }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.pollStatus(transactionId, clientToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.status === 'completed') {\n\t\t\t\tevent.cookies.set('claim_transaction_id', transactionId, {\n\t\t\t\t\t...loginCookieOptions,\n\t\t\t\t\tmaxAge: 60 // 1 minute\n\t\t\t\t});\n\n\t\t\t\treturn json({\n\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\tredirectTo: '/api/auth/claim',\n\t\t\t\t\t...(result.redirect_context != null\n\t\t\t\t\t\t? { redirect_context: result.redirect_context }\n\t\t\t\t\t\t: {})\n\t\t\t\t});\n\t\t\t} else if (result.status === 'pending') {\n\t\t\t\treturn json({ status: 'pending' });\n\t\t\t} else {\n\t\t\t\treturn json({ status: 'expired' });\n\t\t\t}\n\t\t} else {\n\t\t\treturn json({ status: 'error' }, { status: 400 });\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Poll status error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'poll', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ status: 'error', error: extractClientErrorMessage(error, 'Polling failed') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ status: 'error' }, { status: 500 });\n\t}\n}\n\n/**\n * Compute the built-in redirect candidate (before `resolveRedirect` runs).\n * Order: login_redirect_to cookie → SENTINEL_POST_AUTH_REDIRECT env → '/'.\n *\n * Separated from `finalizePostClaimRedirect` so the `ResolveRedirectContext`\n * can surface both the `requestedRedirect` and the `defaultRedirect` cleanly.\n */\nfunction computeBuiltinClaimRedirect(\n\tevent: RequestEvent,\n\tresolved: ResolvedOptions\n): { requested?: string; fallback: string } {\n\tlet requested: string | undefined;\n\tif (resolved.redirectCookie) {\n\t\tconst redirectTo = event.cookies.get('login_redirect_to');\n\t\tif (redirectTo) {\n\t\t\t// Clear it — single use, regardless of which redirect path wins.\n\t\t\tevent.cookies.delete('login_redirect_to', { path: '/' });\n\t\t\trequested = safeRedirectPath(redirectTo);\n\t\t}\n\t}\n\tconst envRedirect = (event.platform as Record<string, unknown>)?.env as\n\t\t| Record<string, string>\n\t\t| undefined;\n\tconst fallback = envRedirect?.SENTINEL_POST_AUTH_REDIRECT\n\t\t? safeRedirectPath(envRedirect.SENTINEL_POST_AUTH_REDIRECT)\n\t\t: '/';\n\treturn { requested, fallback };\n}\n\n/**\n * Finalise the post-claim destination:\n * 1. Compute built-in requested + fallback candidates.\n * 2. If `opts.resolveRedirect` is configured, call it; use the returned path\n * when it is a safe relative path. Throws / empty / unsafe → fallback chain.\n * 3. Otherwise prefer `requested` then `fallback`.\n */\nasync function finalizePostClaimRedirect(\n\tevent: RequestEvent,\n\tresolved: ResolvedOptions,\n\taccessToken: string,\n\trememberMe: boolean\n): Promise<string> {\n\tconst { requested, fallback } = computeBuiltinClaimRedirect(event, resolved);\n\tconst defaultRedirect = requested || fallback;\n\n\tif (!resolved.opts.resolveRedirect) {\n\t\treturn defaultRedirect;\n\t}\n\n\tconst user = userLocalsFromAccessToken(accessToken);\n\tif (!user) {\n\t\tresolved.log.warn('resolveRedirect skipped — access token failed to decode');\n\t\treturn defaultRedirect;\n\t}\n\n\ttry {\n\t\tconst candidate = await resolved.opts.resolveRedirect(event, {\n\t\t\tuser,\n\t\t\taccessToken,\n\t\t\trememberMe,\n\t\t\trequestedRedirect: requested,\n\t\t\tdefaultRedirect\n\t\t});\n\t\tif (typeof candidate === 'string' && isSafeRelativePath(candidate)) {\n\t\t\treturn candidate;\n\t\t}\n\t\tresolved.log.warn('resolveRedirect returned invalid path — using default', {\n\t\t\tcandidate: typeof candidate === 'string' ? candidate : typeof candidate\n\t\t});\n\t} catch (err) {\n\t\tresolved.log.error('resolveRedirect threw — using default', { error: String(err) });\n\t}\n\treturn defaultRedirect;\n}\n\n// Claim Handler (POST)\nasync function handleClaimPost(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tconst transactionId = event.cookies.get('claim_transaction_id');\n\t\tconst clientToken = event.cookies.get('login_client_token');\n\t\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\t\tif (!transactionId) {\n\t\t\treturn json({ success: false, error: 'No transaction ID' }, { status: 400 });\n\t\t}\n\t\tif (!clientToken) {\n\t\t\treturn json({ success: false, error: 'No client token' }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.claimTokens(transactionId, clientToken);\n\n\t\tif ((result.code === 0 || result.success) && result.access_token && result.refresh_token) {\n\t\t\tconst postAuthRedirect = await finalizePostClaimRedirect(\n\t\t\t\tevent,\n\t\t\t\tresolved,\n\t\t\t\tresult.access_token,\n\t\t\t\trememberMe\n\t\t\t);\n\t\t\tsetSessionCookies(event.cookies, result.access_token, result.refresh_token, rememberMe);\n\t\t\tclearLoginCookies(event.cookies);\n\n\t\t\t// onAfterClaim hook — user creation, profile sync, analytics\n\t\t\tif (opts.onAfterClaim) {\n\t\t\t\tawait opts.onAfterClaim(event, {\n\t\t\t\t\taccessToken: result.access_token,\n\t\t\t\t\trefreshToken: result.refresh_token,\n\t\t\t\t\tredirectTo: postAuthRedirect\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn json({ success: true, redirectTo: postAuthRedirect });\n\t\t} else {\n\t\t\treturn json({ success: false, error: result.error || 'Failed to claim tokens' }, { status: 400 });\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Claim tokens error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'claim', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ success: false, error: extractClientErrorMessage(error, 'Failed to claim tokens') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ success: false, error: 'Internal server error' }, { status: 500 });\n\t}\n}\n\n// Claim Handler (GET)\nasync function handleClaimGet(event: RequestEvent, resolved: ResolvedOptions): Promise<never> {\n\tconst { loginRedirect, opts } = resolved;\n\tconst transactionId = event.cookies.get('claim_transaction_id');\n\tconst clientToken = event.cookies.get('login_client_token');\n\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\tif (!transactionId || !clientToken) {\n\t\tthrow redirect(302, `${loginRedirect}?error=no_transaction`);\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\tconst result = await sentinel.claimTokens(transactionId, clientToken);\n\n\tif ((result.code === 0 || result.success) && result.access_token && result.refresh_token) {\n\t\tconst postAuthRedirect = await finalizePostClaimRedirect(\n\t\t\tevent,\n\t\t\tresolved,\n\t\t\tresult.access_token,\n\t\t\trememberMe\n\t\t);\n\t\tsetSessionCookies(event.cookies, result.access_token, result.refresh_token, rememberMe);\n\t\tclearLoginCookies(event.cookies);\n\n\t\tif (opts.onAfterClaim) {\n\t\t\tawait opts.onAfterClaim(event, {\n\t\t\t\taccessToken: result.access_token,\n\t\t\t\trefreshToken: result.refresh_token,\n\t\t\t\tredirectTo: postAuthRedirect\n\t\t\t});\n\t\t}\n\n\t\tthrow redirect(303, postAuthRedirect);\n\t}\n\n\tthrow redirect(302, `${loginRedirect}?error=claim_failed`);\n}\n\n/**\n * Resolve the post-logout destination.\n *\n * - `logoutRedirect` absent → fall back to `loginRedirect` (preserves 1.4.x behaviour).\n * - `logoutRedirect` string → validate; unsafe → fall back to `loginRedirect`.\n * - `logoutRedirect` function → await; validate; throw/empty/unsafe → fall back to `loginRedirect`.\n */\nasync function resolveLogoutDestination(\n\tevent: RequestEvent,\n\tresolved: ResolvedOptions\n): Promise<string> {\n\tconst fallback = resolved.loginRedirect;\n\tconst opt = resolved.opts.logoutRedirect;\n\tif (opt === undefined) return fallback;\n\n\tif (typeof opt === 'string') {\n\t\treturn isSafeRelativePath(opt) ? opt : fallback;\n\t}\n\n\tif (typeof opt === 'function') {\n\t\t// Best-effort user context: decode the still-present access token before\n\t\t// cookies get cleared. Callers typing user as possibly null lines up with\n\t\t// the reality that the token may be missing or malformed.\n\t\tconst accessToken = event.cookies.get(COOKIE_NAMES.ACCESS_TOKEN);\n\t\tconst user = accessToken ? userLocalsFromAccessToken(accessToken) : null;\n\t\ttry {\n\t\t\tconst candidate = await opt(event, { user });\n\t\t\tif (typeof candidate === 'string' && isSafeRelativePath(candidate)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t\tresolved.log.warn('logoutRedirect returned invalid path — using fallback', {\n\t\t\t\tcandidate: typeof candidate === 'string' ? candidate : typeof candidate\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tresolved.log.error('logoutRedirect threw — using fallback', { error: String(err) });\n\t\t}\n\t}\n\treturn fallback;\n}\n\n// Logout Handler\nasync function handleLogout(event: RequestEvent, resolved: ResolvedOptions): Promise<never> {\n\tconst destination = await resolveLogoutDestination(event, resolved);\n\tclearAuthCookies(event.cookies);\n\tthrow redirect(303, destination);\n}\n\n// Refresh Handler\nasync function handleRefresh(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tconst refreshToken = event.cookies.get(COOKIE_NAMES.REFRESH_TOKEN);\n\n\t\tif (!refreshToken) {\n\t\t\treturn json({ success: false, error: 'No refresh token' }, { status: 401 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.refreshToken(refreshToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token) {\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.ACCESS_TOKEN);\n\n\t\t\t\tif (result.refresh_token) {\n\t\t\t\t\tevent.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);\n\t\t\t\t}\n\n\t\t\t\treturn json({ success: true });\n\t\t\t} else {\n\t\t\t\treturn json({ success: false, error: 'No access token returned' }, { status: 400 });\n\t\t\t}\n\t\t} else {\n\t\t\tlog.warn('Token refresh failed - returning error without clearing cookies');\n\t\t\treturn json({ success: false, error: 'Refresh token expired' }, { status: 401 });\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Refresh token error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'refresh', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ success: false, error: extractClientErrorMessage(error, 'Token refresh failed') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ success: false, error: 'Internal server error' }, { status: 500 });\n\t}\n}\n\n// Register Handler\nasync function handleRegister(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tlet email: string;\n\t\tlet redirectContext: string | undefined;\n\t\tconst contentType = event.request.headers.get('content-type') || '';\n\n\t\tif (contentType.includes('application/json')) {\n\t\t\tconst body = await event.request.json();\n\t\t\temail = body.email;\n\t\t\tif (typeof body.redirectContext === 'string') redirectContext = body.redirectContext;\n\t\t} else if (contentType.includes('application/x-www-form-urlencoded') || contentType.includes('multipart/form-data')) {\n\t\t\tconst formData = await event.request.formData();\n\t\t\temail = formData.get('email') as string;\n\t\t\tconst rc = formData.get('redirectContext');\n\t\t\tif (typeof rc === 'string') redirectContext = rc;\n\t\t} else {\n\t\t\tconst body = await event.request.json();\n\t\t\temail = body.email;\n\t\t\tif (typeof body.redirectContext === 'string') redirectContext = body.redirectContext;\n\t\t}\n\n\t\t// Use shared email validation\n\t\tconst emailError = validateEmail(email);\n\t\tif (emailError) {\n\t\t\treturn json({ success: false, error: emailError }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.register(\n\t\t\t(email as string).trim(),\n\t\t\tredirectContext !== undefined ? { redirectContext } : undefined\n\t\t);\n\n\t\tif (result.code !== 0 && !result.success) {\n\t\t\tlog.error('Registration issue', { code: result.code, error: result.error });\n\t\t}\n\n\t\t// Always return same message to prevent user enumeration\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is not already registered, you will receive a verification link shortly.'\n\t\t});\n\t} catch (error) {\n\t\tlog.error('Registration error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'register', error });\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is not already registered, you will receive a verification link shortly.'\n\t\t});\n\t}\n}\n\n// Verify Email Handler\nasync function handleVerify(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tconst { token } = await event.request.json();\n\n\t\tif (!token) {\n\t\t\treturn json({ success: false, error: 'Verification token is required' }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.verifyEmail(token);\n\n\t\treturn json(result);\n\t} catch (error) {\n\t\tlog.error('Verify email error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'verify', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ success: false, error: extractClientErrorMessage(error, 'Verification failed') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ success: false, error: 'Internal server error' }, { status: 500 });\n\t}\n}\n\n/**\n * Creates all auth API route handlers for catch-all route pattern.\n *\n * @param options - Optional configuration for auth handlers.\n *\n * Usage:\n * ```typescript\n * // src/routes/api/auth/[...path]/+server.ts\n * import { createAuthHandlers } from '@nexartis/sentinel-sdk/sveltekit/handlers';\n * export const { GET, POST } = createAuthHandlers({\n * loginCookieTtlSeconds: 1800,\n * loginRedirect: '/auth',\n * redirectCookie: true,\n * onBeforeLogin: async (event, ctx) => {\n * // custom rate limiting, validation, etc.\n * },\n * onAfterClaim: async (event, ctx) => {\n * // create user record, sync profile, etc.\n * }\n * });\n * ```\n */\nexport function createAuthHandlers(options: AuthHandlerOptions = {}): { GET: RequestHandler; POST: RequestHandler } {\n\tconst resolved: ResolvedOptions = {\n\t\tloginCookieOptions: buildLoginCookieOptions(options.loginCookieTtlSeconds ?? DEFAULT_LOGIN_COOKIE_TTL_SECONDS),\n\t\tloginRedirect: options.loginRedirect ?? DEFAULT_LOGIN_REDIRECT,\n\t\tredirectCookie: options.redirectCookie ?? true,\n\t\tlog: options.logger ?? createLogger('sentinel-handlers'),\n\t\topts: options\n\t};\n\n\tconst POST: RequestHandler = async (event) => {\n\t\tconst path = event.params.path || '';\n\n\t\tswitch (path) {\n\t\t\tcase 'start-login':\n\t\t\t\treturn handleStartLogin(event, resolved);\n\t\t\tcase 'poll':\n\t\t\t\treturn handlePoll(event, resolved);\n\t\t\tcase 'claim':\n\t\t\t\treturn handleClaimPost(event, resolved);\n\t\t\tcase 'logout':\n\t\t\t\treturn handleLogout(event, resolved);\n\t\t\tcase 'refresh':\n\t\t\t\treturn handleRefresh(event, resolved);\n\t\t\tcase 'register':\n\t\t\t\treturn handleRegister(event, resolved);\n\t\t\tcase 'verify':\n\t\t\t\treturn handleVerify(event, resolved);\n\t\t\tdefault:\n\t\t\t\treturn json({ error: 'Not found' }, { status: 404 });\n\t\t}\n\t};\n\n\tconst GET: RequestHandler = async (event) => {\n\t\tconst path = event.params.path || '';\n\n\t\tswitch (path) {\n\t\t\tcase 'claim':\n\t\t\t\treturn handleClaimGet(event, resolved);\n\t\t\tcase 'logout':\n\t\t\t\treturn handleLogout(event, resolved);\n\t\t\tdefault:\n\t\t\t\treturn json({ error: 'Not found' }, { status: 404 });\n\t\t}\n\t};\n\n\treturn { GET, POST };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/handlers/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/handlers/index.ts\nimport { json, redirect } from '@sveltejs/kit';\nimport type { RequestHandler, RequestEvent } from '@sveltejs/kit';\nimport { SentinelClient } from '../../core/client.js';\nimport { SentinelApiError } from '../../core/errors.js';\nimport type {\n\tAuthHandlerOptions,\n\tDecodedToken,\n\tUserLocals\n} from '../../core/types.js';\nimport { decodeTokenLocally } from '../../core/auth-utils.js';\nimport { createLogger, type SentinelLogger } from '../../core/logger.js';\nimport {\n\tsetSessionCookies,\n\tsetCommunityAccessCookie,\n\tclearAuthCookies,\n\tCOOKIE_NAMES,\n\tCOOKIE_OPTIONS\n} from '../cookies.js';\nimport { getConfigFromPlatform, clearLoginCookies } from '../utils.js';\n\nconst DEFAULT_LOGIN_COOKIE_TTL_SECONDS = 600; // 10 minutes\nconst DEFAULT_LOGIN_REDIRECT = '/auth';\nconst MAX_EMAIL_LENGTH = 254; // RFC 5321\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Resolved options with defaults applied. Computed once in createAuthHandlers. */\ninterface ResolvedOptions {\n\tloginCookieOptions: ReturnType<typeof buildLoginCookieOptions>;\n\tloginRedirect: string;\n\tredirectCookie: boolean;\n\tlog: SentinelLogger;\n\topts: AuthHandlerOptions;\n}\n\n/**\n * True if `path` is a safe relative path: starts with `/`, is not `//x` or\n * `/\\x` (protocol-relative), carries no scheme prefix (http:, javascript:, data:),\n * and contains no whitespace or control characters (defence-in-depth against\n * header-injection via CR/LF/tab smuggled through a `Location` header).\n */\nfunction isSafeRelativePath(path: string): boolean {\n\tif (typeof path !== 'string' || path.length === 0) return false;\n\tif (!path.startsWith('/')) return false;\n\tif (/^[/\\\\]{2}/.test(path)) return false;\n\tif (/^\\/[a-z][a-z0-9+.\\-]*:/i.test(path)) return false;\n\t// Reject any ASCII control char (incl. CR/LF/TAB) or whitespace.\n\tif (/[\\s\\u0000-\\u001f\\u007f]/.test(path)) return false;\n\treturn true;\n}\n\n/** Sanitise a redirect path to prevent open-redirect via env misconfiguration. */\nfunction safeRedirectPath(raw: string | undefined): string {\n\tconst path = (raw || '/').trim();\n\tif (!isSafeRelativePath(path)) return '/';\n\treturn path;\n}\n\n/** Build UserLocals from a freshly-issued access token, or null if the token won't decode. */\nfunction userLocalsFromAccessToken(accessToken: string): UserLocals | null {\n\tconst decoded: DecodedToken | null = decodeTokenLocally(accessToken);\n\tif (!decoded) return null;\n\treturn {\n\t\tisAuthenticated: true,\n\t\tid: decoded.sub,\n\t\temail: decoded.email,\n\t\t// TRUSTIP-147: roles may be absent on post-cutover tokens — keep a\n\t\t// defined array for existing consumers.\n\t\troles: decoded.roles || [],\n\t\tplatformAdmin: decoded.platform_admin === true\n\t};\n}\n\nfunction buildLoginCookieOptions(ttlSeconds: number) {\n\treturn {\n\t\tpath: '/',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: ttlSeconds\n\t};\n}\n\n/** Validate email: present, non-empty string, max 254 chars, basic format. */\nfunction validateEmail(email: unknown): string | null {\n\tif (!email || typeof email !== 'string') return 'Email is required';\n\tconst trimmed = email.trim();\n\tif (!trimmed) return 'Email is required';\n\tif (trimmed.length > MAX_EMAIL_LENGTH) return 'Email exceeds maximum length';\n\tif (!EMAIL_REGEX.test(trimmed)) return 'Invalid email format';\n\treturn null; // valid\n}\n\n// Start Login Handler\nasync function handleStartLogin(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { loginCookieOptions, log, opts } = resolved;\n\ttry {\n\t\tconst body = await event.request.json();\n\t\tconst { email, rememberMe, redirectTo, redirectContext, audience, communityId } = body;\n\n\t\t// Built-in basic email validation\n\t\tconst emailError = validateEmail(email);\n\t\tif (emailError) {\n\t\t\treturn json({ success: false, error: emailError }, { status: 400 });\n\t\t}\n\t\tconst trimmedEmail = (email as string).trim();\n\n\t\t// onBeforeLogin hook — custom validation, rate limiting, etc.\n\t\tif (opts.onBeforeLogin) {\n\t\t\tconst rejection = await opts.onBeforeLogin(event, {\n\t\t\t\temail: trimmedEmail,\n\t\t\t\trememberMe: !!rememberMe,\n\t\t\t\tredirectTo,\n\t\t\t\taudience:\n\t\t\t\t\taudience === 'site_owner' || audience === 'fan' || audience === 'admin'\n\t\t\t\t\t\t? audience\n\t\t\t\t\t\t: undefined,\n\t\t\t\tcommunityId: typeof communityId === 'string' ? communityId : undefined\n\t\t\t});\n\t\t\tif (rejection) {\n\t\t\t\t// 1.5.0: if the rejection carries a safe relative `redirect`, 303\n\t\t\t\t// to that branded destination instead of returning a JSON error.\n\t\t\t\t// 303 (See Other) converts the POST into a GET on the client.\n\t\t\t\tif (rejection.redirect && isSafeRelativePath(rejection.redirect)) {\n\t\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\t\treturn new Response(null, {\n\t\t\t\t\t\tstatus: 303,\n\t\t\t\t\t\theaders: { Location: rejection.redirect }\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (rejection.redirect) {\n\t\t\t\t\tlog.warn('onBeforeLogin returned unsafe redirect — falling back to JSON', {\n\t\t\t\t\t\tredirect: rejection.redirect\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// Clear stale login cookies on JSON reject so a rejected login never\n\t\t\t\t// leaves a half-open transaction behind. Mirrors the 303 path above.\n\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\treturn json({ success: false, error: rejection.error }, { status: rejection.status ?? 400 });\n\t\t\t}\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst startLoginOpts: {\n\t\t\tredirectContext?: string;\n\t\t\trememberMe?: boolean;\n\t\t\taudience?: 'site_owner' | 'fan' | 'admin';\n\t\t\tcommunityId?: string;\n\t\t} = {};\n\t\tif (typeof redirectContext === 'string') {\n\t\t\tstartLoginOpts.redirectContext = redirectContext;\n\t\t}\n\t\tif (typeof rememberMe === 'boolean') {\n\t\t\tstartLoginOpts.rememberMe = rememberMe;\n\t\t}\n\t\tif (audience === 'site_owner' || audience === 'fan' || audience === 'admin') {\n\t\t\tstartLoginOpts.audience = audience;\n\t\t}\n\t\tif (typeof communityId === 'string' && communityId.trim()) {\n\t\t\tstartLoginOpts.communityId = communityId.trim();\n\t\t}\n\t\tconst result = await sentinel.startLogin(trimmedEmail, startLoginOpts);\n\n\t\t// cookiesSet requires both a success signal AND valid tokens to prevent\n\t\t// setting undefined/null cookie values if the API response is malformed.\n\t\tconst cookiesSet = Boolean(\n\t\t\t(result.code === 0 || result.success) &&\n\t\t\tresult.transactionId &&\n\t\t\tresult.clientToken\n\t\t);\n\n\t\tif (cookiesSet) {\n\t\t\tevent.cookies.set('login_transaction_id', result.transactionId!, loginCookieOptions);\n\t\t\tevent.cookies.set('login_client_token', result.clientToken!, loginCookieOptions);\n\t\t\tevent.cookies.set('login_remember_me', rememberMe ? 'true' : 'false', loginCookieOptions);\n\n\t\t\t// Built-in redirectTo cookie — stores where to send user after login\n\t\t\tif (resolved.redirectCookie && redirectTo) {\n\t\t\t\tconst safeTarget = safeRedirectPath(redirectTo);\n\t\t\t\tif (safeTarget !== '/') {\n\t\t\t\t\tevent.cookies.set('login_redirect_to', safeTarget, loginCookieOptions);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// onAfterLogin hook — audit logging, analytics, etc.\n\t\t\tif (opts.onAfterLogin) {\n\t\t\t\tawait opts.onAfterLogin(event, {\n\t\t\t\t\temail: trimmedEmail,\n\t\t\t\t\ttransactionId: result.transactionId!\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tlog.error('Start login issue', { code: result.code, error: result.error });\n\t\t\tclearLoginCookies(event.cookies);\n\t\t}\n\n\t\t// Always return same message to prevent user enumeration.\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is registered, you will receive a magic link shortly.',\n\t\t\tpollReady: cookiesSet\n\t\t});\n\t} catch (error) {\n\t\tlog.error('Start login error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'start-login', error });\n\t\tclearLoginCookies(event.cookies);\n\n\t\t// Fail loud on configuration / client errors so misconfigured origins or\n\t\t// invalid requests are surfaced immediately instead of masking as success.\n\t\tif (error instanceof SentinelApiError && error.status === 400) {\n\t\t\tconst body = error.body as Record<string, any> | undefined;\n\t\t\tif (body && typeof body === 'object' && (body.code === 4003 || body.code === 'EMAIL_ENDPOINT_UNAPPROVED')) {\n\t\t\t\treturn json(\n\t\t\t\t\t{\n\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\terror: body.message || 'Email endpoint host is not approved'\n\t\t\t\t\t},\n\t\t\t\t\t{ status: 400 }\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is registered, you will receive a magic link shortly.',\n\t\t\tpollReady: false\n\t\t});\n\t}\n}\n\nfunction extractClientErrorMessage(error: SentinelApiError, defaultMsg: string): string {\n\tconst body = error.body;\n\tif (typeof body === 'string' && body.trim().length > 0) {\n\t\treturn body;\n\t}\n\tif (typeof body === 'object' && body !== null) {\n\t\tconst rec = body as Record<string, any>;\n\t\tif (typeof rec.message === 'string' && rec.message.length > 0) return rec.message;\n\t\tif (typeof rec.error === 'string' && rec.error.length > 0) return rec.error;\n\t}\n\treturn defaultMsg;\n}\n\n// Poll Handler\nasync function handlePoll(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { loginCookieOptions, log, opts } = resolved;\n\ttry {\n\t\tconst transactionId = event.cookies.get('login_transaction_id');\n\t\tconst clientToken = event.cookies.get('login_client_token');\n\n\t\tif (!transactionId) {\n\t\t\treturn json({ status: 'expired' }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.pollStatus(transactionId, clientToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.status === 'completed') {\n\t\t\t\tevent.cookies.set('claim_transaction_id', transactionId, {\n\t\t\t\t\t...loginCookieOptions,\n\t\t\t\t\tmaxAge: 60 // 1 minute\n\t\t\t\t});\n\n\t\t\t\treturn json({\n\t\t\t\t\tstatus: 'completed',\n\t\t\t\t\tredirectTo: '/api/auth/claim',\n\t\t\t\t\t...(result.redirect_context != null\n\t\t\t\t\t\t? { redirect_context: result.redirect_context }\n\t\t\t\t\t\t: {})\n\t\t\t\t});\n\t\t\t} else if (result.status === 'pending') {\n\t\t\t\treturn json({ status: 'pending' });\n\t\t\t} else {\n\t\t\t\treturn json({ status: 'expired' });\n\t\t\t}\n\t\t} else {\n\t\t\treturn json({ status: 'error' }, { status: 400 });\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Poll status error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'poll', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ status: 'error', error: extractClientErrorMessage(error, 'Polling failed') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ status: 'error' }, { status: 500 });\n\t}\n}\n\n/**\n * Compute the built-in redirect candidate (before `resolveRedirect` runs).\n * Order: login_redirect_to cookie → SENTINEL_POST_AUTH_REDIRECT env → '/'.\n *\n * Separated from `finalizePostClaimRedirect` so the `ResolveRedirectContext`\n * can surface both the `requestedRedirect` and the `defaultRedirect` cleanly.\n */\nfunction computeBuiltinClaimRedirect(\n\tevent: RequestEvent,\n\tresolved: ResolvedOptions\n): { requested?: string; fallback: string } {\n\tlet requested: string | undefined;\n\tif (resolved.redirectCookie) {\n\t\tconst redirectTo = event.cookies.get('login_redirect_to');\n\t\tif (redirectTo) {\n\t\t\t// Clear it — single use, regardless of which redirect path wins.\n\t\t\tevent.cookies.delete('login_redirect_to', { path: '/' });\n\t\t\trequested = safeRedirectPath(redirectTo);\n\t\t}\n\t}\n\tconst envRedirect = (event.platform as Record<string, unknown>)?.env as\n\t\t| Record<string, string>\n\t\t| undefined;\n\tconst fallback = envRedirect?.SENTINEL_POST_AUTH_REDIRECT\n\t\t? safeRedirectPath(envRedirect.SENTINEL_POST_AUTH_REDIRECT)\n\t\t: '/';\n\treturn { requested, fallback };\n}\n\n/**\n * Finalise the post-claim destination:\n * 1. Compute built-in requested + fallback candidates.\n * 2. If `opts.resolveRedirect` is configured, call it; use the returned path\n * when it is a safe relative path. Throws / empty / unsafe → fallback chain.\n * 3. Otherwise prefer `requested` then `fallback`.\n */\nasync function finalizePostClaimRedirect(\n\tevent: RequestEvent,\n\tresolved: ResolvedOptions,\n\taccessToken: string,\n\trememberMe: boolean\n): Promise<string> {\n\tconst { requested, fallback } = computeBuiltinClaimRedirect(event, resolved);\n\tconst defaultRedirect = requested || fallback;\n\n\tif (!resolved.opts.resolveRedirect) {\n\t\treturn defaultRedirect;\n\t}\n\n\tconst user = userLocalsFromAccessToken(accessToken);\n\tif (!user) {\n\t\tresolved.log.warn('resolveRedirect skipped — access token failed to decode');\n\t\treturn defaultRedirect;\n\t}\n\n\ttry {\n\t\tconst candidate = await resolved.opts.resolveRedirect(event, {\n\t\t\tuser,\n\t\t\taccessToken,\n\t\t\trememberMe,\n\t\t\trequestedRedirect: requested,\n\t\t\tdefaultRedirect\n\t\t});\n\t\tif (typeof candidate === 'string' && isSafeRelativePath(candidate)) {\n\t\t\treturn candidate;\n\t\t}\n\t\tresolved.log.warn('resolveRedirect returned invalid path — using default', {\n\t\t\tcandidate: typeof candidate === 'string' ? candidate : typeof candidate\n\t\t});\n\t} catch (err) {\n\t\tresolved.log.error('resolveRedirect threw — using default', { error: String(err) });\n\t}\n\treturn defaultRedirect;\n}\n\n// Claim Handler (POST)\nasync function handleClaimPost(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tconst transactionId = event.cookies.get('claim_transaction_id');\n\t\tconst clientToken = event.cookies.get('login_client_token');\n\t\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\t\tif (!transactionId) {\n\t\t\treturn json({ success: false, error: 'No transaction ID' }, { status: 400 });\n\t\t}\n\t\tif (!clientToken) {\n\t\t\treturn json({ success: false, error: 'No client token' }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.claimTokens(transactionId, clientToken);\n\n\t\tif ((result.code === 0 || result.success) && result.access_token && result.refresh_token) {\n\t\t\tconst postAuthRedirect = await finalizePostClaimRedirect(\n\t\t\t\tevent,\n\t\t\t\tresolved,\n\t\t\t\tresult.access_token,\n\t\t\t\trememberMe\n\t\t\t);\n\t\t\tsetSessionCookies(event.cookies, result.access_token, result.refresh_token, rememberMe);\n\t\t\tif (result.community_access_token) {\n\t\t\t\tsetCommunityAccessCookie(event.cookies, result.community_access_token);\n\t\t\t}\n\t\t\tclearLoginCookies(event.cookies);\n\n\t\t\t// onAfterClaim hook — user creation, profile sync, analytics\n\t\t\tif (opts.onAfterClaim) {\n\t\t\t\tawait opts.onAfterClaim(event, {\n\t\t\t\t\taccessToken: result.access_token,\n\t\t\t\t\trefreshToken: result.refresh_token,\n\t\t\t\t\tredirectTo: postAuthRedirect,\n\t\t\t\t\tcommunityAccessToken: result.community_access_token,\n\t\t\t\t\tcommunityId: result.community_id,\n\t\t\t\t\tcommunityScope: result.community_scope\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn json({ success: true, redirectTo: postAuthRedirect });\n\t\t} else {\n\t\t\treturn json({ success: false, error: result.error || 'Failed to claim tokens' }, { status: 400 });\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Claim tokens error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'claim', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ success: false, error: extractClientErrorMessage(error, 'Failed to claim tokens') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ success: false, error: 'Internal server error' }, { status: 500 });\n\t}\n}\n\n// Claim Handler (GET)\nasync function handleClaimGet(event: RequestEvent, resolved: ResolvedOptions): Promise<never> {\n\tconst { loginRedirect, opts } = resolved;\n\tconst transactionId = event.cookies.get('claim_transaction_id');\n\tconst clientToken = event.cookies.get('login_client_token');\n\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\tif (!transactionId || !clientToken) {\n\t\tthrow redirect(302, `${loginRedirect}?error=no_transaction`);\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\tconst result = await sentinel.claimTokens(transactionId, clientToken);\n\n\tif ((result.code === 0 || result.success) && result.access_token && result.refresh_token) {\n\t\tconst postAuthRedirect = await finalizePostClaimRedirect(\n\t\t\tevent,\n\t\t\tresolved,\n\t\t\tresult.access_token,\n\t\t\trememberMe\n\t\t);\n\t\tsetSessionCookies(event.cookies, result.access_token, result.refresh_token, rememberMe);\n\t\tif (result.community_access_token) {\n\t\t\tsetCommunityAccessCookie(event.cookies, result.community_access_token);\n\t\t}\n\t\tclearLoginCookies(event.cookies);\n\n\t\tif (opts.onAfterClaim) {\n\t\t\tawait opts.onAfterClaim(event, {\n\t\t\t\taccessToken: result.access_token,\n\t\t\t\trefreshToken: result.refresh_token,\n\t\t\t\tcommunityAccessToken: result.community_access_token,\n\t\t\t\tcommunityId: result.community_id,\n\t\t\t\tcommunityScope: result.community_scope,\n\t\t\t\tredirectTo: postAuthRedirect\n\t\t\t});\n\t\t}\n\n\t\tthrow redirect(303, postAuthRedirect);\n\t}\n\n\tthrow redirect(302, `${loginRedirect}?error=claim_failed`);\n}\n\n/**\n * Resolve the post-logout destination.\n *\n * - `logoutRedirect` absent → fall back to `loginRedirect` (preserves 1.4.x behaviour).\n * - `logoutRedirect` string → validate; unsafe → fall back to `loginRedirect`.\n * - `logoutRedirect` function → await; validate; throw/empty/unsafe → fall back to `loginRedirect`.\n */\nasync function resolveLogoutDestination(\n\tevent: RequestEvent,\n\tresolved: ResolvedOptions\n): Promise<string> {\n\tconst fallback = resolved.loginRedirect;\n\tconst opt = resolved.opts.logoutRedirect;\n\tif (opt === undefined) return fallback;\n\n\tif (typeof opt === 'string') {\n\t\treturn isSafeRelativePath(opt) ? opt : fallback;\n\t}\n\n\tif (typeof opt === 'function') {\n\t\t// Best-effort user context: decode the still-present access token before\n\t\t// cookies get cleared. Callers typing user as possibly null lines up with\n\t\t// the reality that the token may be missing or malformed.\n\t\tconst accessToken = event.cookies.get(COOKIE_NAMES.ACCESS_TOKEN);\n\t\tconst user = accessToken ? userLocalsFromAccessToken(accessToken) : null;\n\t\ttry {\n\t\t\tconst candidate = await opt(event, { user });\n\t\t\tif (typeof candidate === 'string' && isSafeRelativePath(candidate)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t\tresolved.log.warn('logoutRedirect returned invalid path — using fallback', {\n\t\t\t\tcandidate: typeof candidate === 'string' ? candidate : typeof candidate\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tresolved.log.error('logoutRedirect threw — using fallback', { error: String(err) });\n\t\t}\n\t}\n\treturn fallback;\n}\n\n// Logout Handler\nasync function handleLogout(event: RequestEvent, resolved: ResolvedOptions): Promise<never> {\n\tconst destination = await resolveLogoutDestination(event, resolved);\n\tclearAuthCookies(event.cookies);\n\tthrow redirect(303, destination);\n}\n\n// Refresh Handler\nasync function handleRefresh(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tconst refreshToken = event.cookies.get(COOKIE_NAMES.REFRESH_TOKEN);\n\n\t\tif (!refreshToken) {\n\t\t\treturn json({ success: false, error: 'No refresh token' }, { status: 401 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.refreshToken(refreshToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token) {\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.ACCESS_TOKEN);\n\n\t\t\t\tif (result.refresh_token) {\n\t\t\t\t\tevent.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);\n\t\t\t\t}\n\n\t\t\t\treturn json({ success: true });\n\t\t\t} else {\n\t\t\t\treturn json({ success: false, error: 'No access token returned' }, { status: 400 });\n\t\t\t}\n\t\t} else {\n\t\t\tlog.warn('Token refresh failed - returning error without clearing cookies');\n\t\t\treturn json({ success: false, error: 'Refresh token expired' }, { status: 401 });\n\t\t}\n\t} catch (error) {\n\t\tlog.error('Refresh token error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'refresh', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ success: false, error: extractClientErrorMessage(error, 'Token refresh failed') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ success: false, error: 'Internal server error' }, { status: 500 });\n\t}\n}\n\n// Register Handler\nasync function handleRegister(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tlet email: string;\n\t\tlet redirectContext: string | undefined;\n\t\tconst contentType = event.request.headers.get('content-type') || '';\n\n\t\tif (contentType.includes('application/json')) {\n\t\t\tconst body = await event.request.json();\n\t\t\temail = body.email;\n\t\t\tif (typeof body.redirectContext === 'string') redirectContext = body.redirectContext;\n\t\t} else if (contentType.includes('application/x-www-form-urlencoded') || contentType.includes('multipart/form-data')) {\n\t\t\tconst formData = await event.request.formData();\n\t\t\temail = formData.get('email') as string;\n\t\t\tconst rc = formData.get('redirectContext');\n\t\t\tif (typeof rc === 'string') redirectContext = rc;\n\t\t} else {\n\t\t\tconst body = await event.request.json();\n\t\t\temail = body.email;\n\t\t\tif (typeof body.redirectContext === 'string') redirectContext = body.redirectContext;\n\t\t}\n\n\t\t// Use shared email validation\n\t\tconst emailError = validateEmail(email);\n\t\tif (emailError) {\n\t\t\treturn json({ success: false, error: emailError }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.register(\n\t\t\t(email as string).trim(),\n\t\t\tredirectContext !== undefined ? { redirectContext } : undefined\n\t\t);\n\n\t\tif (result.code !== 0 && !result.success) {\n\t\t\tlog.error('Registration issue', { code: result.code, error: result.error });\n\t\t}\n\n\t\t// Always return same message to prevent user enumeration\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is not already registered, you will receive a verification link shortly.'\n\t\t});\n\t} catch (error) {\n\t\tlog.error('Registration error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'register', error });\n\t\treturn json({\n\t\t\tsuccess: true,\n\t\t\tmessage: 'If this email is not already registered, you will receive a verification link shortly.'\n\t\t});\n\t}\n}\n\n// Verify Email Handler\nasync function handleVerify(event: RequestEvent, resolved: ResolvedOptions): Promise<Response> {\n\tconst { log, opts } = resolved;\n\ttry {\n\t\tconst { token } = await event.request.json();\n\n\t\tif (!token) {\n\t\t\treturn json({ success: false, error: 'Verification token is required' }, { status: 400 });\n\t\t}\n\n\t\tconst config = getConfigFromPlatform(event.platform);\n\t\tconst sentinel = new SentinelClient(config, event.fetch);\n\t\tconst result = await sentinel.verifyEmail(token);\n\n\t\treturn json(result);\n\t} catch (error) {\n\t\tlog.error('Verify email error', { error: String(error) });\n\t\tif (opts.onError) await opts.onError(event, { handler: 'verify', error });\n\t\tif (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {\n\t\t\treturn json(\n\t\t\t\t{ success: false, error: extractClientErrorMessage(error, 'Verification failed') },\n\t\t\t\t{ status: error.status }\n\t\t\t);\n\t\t}\n\t\treturn json({ success: false, error: 'Internal server error' }, { status: 500 });\n\t}\n}\n\n/**\n * Creates all auth API route handlers for catch-all route pattern.\n *\n * @param options - Optional configuration for auth handlers.\n *\n * Usage:\n * ```typescript\n * // src/routes/api/auth/[...path]/+server.ts\n * import { createAuthHandlers } from '@nexartis/sentinel-sdk/sveltekit/handlers';\n * export const { GET, POST } = createAuthHandlers({\n * loginCookieTtlSeconds: 1800,\n * loginRedirect: '/auth',\n * redirectCookie: true,\n * onBeforeLogin: async (event, ctx) => {\n * // custom rate limiting, validation, etc.\n * },\n * onAfterClaim: async (event, ctx) => {\n * // create user record, sync profile, etc.\n * }\n * });\n * ```\n */\nexport function createAuthHandlers(options: AuthHandlerOptions = {}): { GET: RequestHandler; POST: RequestHandler } {\n\tconst resolved: ResolvedOptions = {\n\t\tloginCookieOptions: buildLoginCookieOptions(options.loginCookieTtlSeconds ?? DEFAULT_LOGIN_COOKIE_TTL_SECONDS),\n\t\tloginRedirect: options.loginRedirect ?? DEFAULT_LOGIN_REDIRECT,\n\t\tredirectCookie: options.redirectCookie ?? true,\n\t\tlog: options.logger ?? createLogger('sentinel-handlers'),\n\t\topts: options\n\t};\n\n\tconst POST: RequestHandler = async (event) => {\n\t\tconst path = event.params.path || '';\n\n\t\tswitch (path) {\n\t\t\tcase 'start-login':\n\t\t\t\treturn handleStartLogin(event, resolved);\n\t\t\tcase 'poll':\n\t\t\t\treturn handlePoll(event, resolved);\n\t\t\tcase 'claim':\n\t\t\t\treturn handleClaimPost(event, resolved);\n\t\t\tcase 'logout':\n\t\t\t\treturn handleLogout(event, resolved);\n\t\t\tcase 'refresh':\n\t\t\t\treturn handleRefresh(event, resolved);\n\t\t\tcase 'register':\n\t\t\t\treturn handleRegister(event, resolved);\n\t\t\tcase 'verify':\n\t\t\t\treturn handleVerify(event, resolved);\n\t\t\tdefault:\n\t\t\t\treturn json({ error: 'Not found' }, { status: 404 });\n\t\t}\n\t};\n\n\tconst GET: RequestHandler = async (event) => {\n\t\tconst path = event.params.path || '';\n\n\t\tswitch (path) {\n\t\t\tcase 'claim':\n\t\t\t\treturn handleClaimGet(event, resolved);\n\t\t\tcase 'logout':\n\t\t\t\treturn handleLogout(event, resolved);\n\t\t\tdefault:\n\t\t\t\treturn json({ error: 'Not found' }, { status: 404 });\n\t\t}\n\t};\n\n\treturn { GET, POST };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC5C,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CAwDjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC9C,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CA2CjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC3C,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CAgDjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"AAoCA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC5C,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CAwDjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC9C,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CA2CjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC3C,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CA8CjB"}
@@ -4,12 +4,25 @@ import { SentinelClient } from '../../core/client.js';
4
4
  import { setSessionCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';
5
5
  import { getConfigFromPlatform, clearLoginCookies } from '../utils.js';
6
6
  import { validateToken } from '../../core/auth-utils.js';
7
- /** Sanitise a redirect path to prevent open-redirect via env misconfiguration. */
7
+ /**
8
+ * Sanitise a redirect path to prevent open-redirect via env misconfiguration or
9
+ * a crafted `?redirect=` value. Only single-slash relative paths are allowed.
10
+ * Rejects protocol-relative ('//x', '/\x'), percent-encoded backslashes ('%5c'),
11
+ * and any string carrying whitespace or ASCII control characters — browsers strip
12
+ * tabs/newlines while parsing a URL, so `/\t/evil.com` would otherwise normalise
13
+ * to `//evil.com` and redirect cross-origin.
14
+ */
8
15
  function safeRedirectPath(raw) {
9
16
  const path = (raw || '/').trim();
10
- if (/^[/\\]{2}/.test(path) || /^[a-z][a-z0-9+.-]*:/i.test(path))
17
+ if (!path.startsWith('/'))
18
+ return '/';
19
+ if (/^[/\\]{2}/.test(path))
20
+ return '/';
21
+ if (/[\s\u0000-\u001f\u007f]/.test(path))
22
+ return '/';
23
+ if (path.includes('\\') || path.toLowerCase().includes('%5c'))
11
24
  return '/';
12
- return path.startsWith('/') ? path : '/';
25
+ return path;
13
26
  }
14
27
  /**
15
28
  * Detects SvelteKit redirects using ALL THREE methods.
@@ -162,14 +175,12 @@ export async function handleVerifyEmail(event) {
162
175
  export async function handleVerified(event) {
163
176
  const accessToken = event.cookies.get(COOKIE_NAMES.VERIFY_ACCESS_TOKEN);
164
177
  const refreshToken = event.cookies.get(COOKIE_NAMES.VERIFY_REFRESH_TOKEN);
165
- // Sanitize redirect target to prevent open-redirect attacks.
166
- // Only allow relative paths starting with a single '/'. Reject protocol-relative
167
- // URLs ('//evil.com'), absolute URLs ('https://evil.com'), backslash variants
168
- // ('/\evil.com' which some user agents normalise to '//evil.com'), percent-encoded
169
- // backslashes ('%5c' / '%5C' which browsers may decode and normalise the same way),
170
- // and bare strings.
178
+ // Sanitize redirect target to prevent open-redirect attacks. Only relative
179
+ // paths starting with a single '/' survive; protocol-relative URLs, absolute
180
+ // URLs, backslash variants, percent-encoded backslashes, and control/whitespace
181
+ // characters (which browsers strip during URL parsing) all fall back to '/'.
171
182
  const rawRedirect = event.url.searchParams.get('redirect') || '/';
172
- const redirectTo = (rawRedirect.startsWith('/') && !rawRedirect.startsWith('//') && !rawRedirect.includes('\\') && !rawRedirect.toLowerCase().includes('%5c')) ? rawRedirect : '/';
183
+ const redirectTo = safeRedirectPath(rawRedirect);
173
184
  if (!accessToken || !refreshToken) {
174
185
  // Clear any partial verify cookies to avoid stale state within the TTL
175
186
  event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });