@nexartis/sentinel-sdk 1.9.2 → 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.
- package/README.md +32 -8
- package/dist/core/auth-utils.d.ts.map +1 -1
- package/dist/core/auth-utils.js +23 -3
- package/dist/core/auth-utils.js.map +1 -1
- package/dist/core/client.d.ts +4 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +6 -0
- package/dist/core/client.js.map +1 -1
- package/dist/core/consumer-version-floor.d.ts +56 -0
- package/dist/core/consumer-version-floor.d.ts.map +1 -0
- package/dist/core/consumer-version-floor.js +233 -0
- package/dist/core/consumer-version-floor.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/jwks.d.ts +8 -1
- package/dist/core/jwks.d.ts.map +1 -1
- package/dist/core/jwks.js +28 -4
- package/dist/core/jwks.js.map +1 -1
- package/dist/core/types.d.ts +35 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/sveltekit/cookies.d.ts +10 -0
- package/dist/sveltekit/cookies.d.ts.map +1 -1
- package/dist/sveltekit/cookies.js +13 -0
- package/dist/sveltekit/cookies.js.map +1 -1
- package/dist/sveltekit/handle.js +8 -2
- package/dist/sveltekit/handle.js.map +1 -1
- package/dist/sveltekit/handlers/index.d.ts.map +1 -1
- package/dist/sveltekit/handlers/index.js +30 -5
- package/dist/sveltekit/handlers/index.js.map +1 -1
- package/dist/sveltekit/pages/index.d.ts.map +1 -1
- package/dist/sveltekit/pages/index.js +21 -10
- package/dist/sveltekit/pages/index.js.map +1 -1
- package/package.json +8 -3
|
@@ -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":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAqB,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAKtF;;;;;;;IAOI;AACJ,MAAM,eAAe,GAAG,2BAA2B,CAAC;AAEpD,IAAI,wBAAwB,GAAG,KAAK,CAAC;AACrC,SAAS,kBAAkB,CAAC,GAAmB;IAC9C,IAAI,wBAAwB;QAAE,OAAO;IACrC,wBAAwB,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,IAAI,CACP,GAAG,eAAe,uDAAuD;QACxE,qEAAqE,CACtE,CAAC;AACH,CAAC;AAED,IAAI,wBAAwB,GAAG,KAAK,CAAC;AACrC,SAAS,4BAA4B,CAAC,GAAmB;IACxD,IAAI,wBAAwB;QAAE,OAAO;IACrC,wBAAwB,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,KAAK,CACR,GAAG,eAAe,iDAAiD;QAClE,mDAAmD;QACnD,kDAAkD,CACnD,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,gCAAgC;IAC/C,wBAAwB,GAAG,KAAK,CAAC;IACjC,wBAAwB,GAAG,KAAK,CAAC;AAClC,CAAC;AAQD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAiC,EAAE;IACvE,MAAM,EACL,eAAe,GAAG,EAAE,EACpB,aAAa,GAAG,OAAO,EACvB,GAAG,OAAO,CAAC;IACZ,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAE9D,qFAAqF;IACrF,IACC,OAAO,CAAC,cAAc,KAAK,IAAI;QAC/B,OAAO,OAAO,KAAK,WAAW;QAC9B,OAAO,CAAC,GAAG,EAAE,QAAQ,KAAK,YAAY,EACrC,CAAC;QACF,MAAM,IAAI,KAAK,CACd,GAAG,eAAe,gDAAgD;YACjE,yEAAyE,CAC1E,CAAC;IACH,CAAC;IAED,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;QACnC,qCAAqC;QACrC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAwB,CAAC;QAC9C,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QACnB,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC;QAE1B,qEAAqE;QACrE,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAErE,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACtE,IAAI,UAAU;YAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAEjF,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QACjE,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;QAEnE,IAAI,WAAW,EAAE,CAAC;YACjB,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;gBACvE,IAAI,OAAO,EAAE,CAAC;oBACb,MAAM,CAAC,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;oBACxC,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;gBAClC,CAAC;qBAAM,CAAC;oBACP,MAAM,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;gBACnE,CAAC;YACF,CAAC;iBAAM,IAAI,YAAY,EAAE,CAAC;gBACzB,MAAM,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YACnE,CAAC;QACF,CAAC;aAAM,IAAI,YAAY,EAAE,CAAC;YACzB,MAAM,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QACnE,CAAC;QAED,6DAA6D;QAC7D,MAAM,gBAAgB,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CACrD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CACpC,CAAC;QAEF,IAAI,gBAAgB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;YACzD,MAAM,WAAW,GAAG,GAAG,aAAa,aAAa,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC;YAClF,OAAO,IAAI,QAAQ,CAAC,UAAU,EAAE;gBAC/B,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE;aAClC,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QAEtC,uBAAuB;QACvB,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAChD,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;QAC1D,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,iCAAiC,CAAC,CAAC;QAE3E,OAAO,QAAQ,CAAC;IACjB,CAAC,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,oBAAoB,CAClC,WAAmB,EACnB,QAA6B,EAC7B,GAAmB;IAEnB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,OAAO,CAAC;IACxB,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,iDAAiD,EAAE;QAC3D,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,OAAO,CAAC,MAAM;KACtB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,uBAAuB,CAAC,QAA4C;IAC5E,MAAM,WAAW,GAAI,QAAgC,EAAE,GAAyC,CAAC;IACjG,MAAM,YAAY,GAAG,WAAW,EAAE,QAAQ,CAAC;IAC3C,MAAM,WAAW,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACvF,OAAO,CAAC,YAAY,IAAI,WAAW,CAAC,KAAK,YAAY,CAAC;AACvD,CAAC;AAED,SAAS,oBAAoB,CAC5B,OAA8B,EAC9B,QAA4C,EAC5C,GAAmB;IAEnB,IAAI,OAAO,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;QACrC,IAAI,uBAAuB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvC,4BAA4B,CAAC,GAAG,CAAC,CAAC;YAClC,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,WAAW,GAAI,QAAgC,EAAE,GAAyC,CAAC;IACjG,MAAM,YAAY,GAAG,WAAW,EAAE,CAAC,eAAe,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChG,MAAM,KAAK,GAAG,YAAY,IAAI,WAAW,CAAC;IAC1C,MAAM,SAAS,GAAG,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM,CAAC;IACpD,IAAI,SAAS,IAAI,uBAAuB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,4BAA4B,CAAC,GAAG,CAAC,CAAC;QAClC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,cAAc,CACtB,OAA8B,EAC9B,MAAsB,EACtB,KAAmB,EACnB,GAAmB;IAEnB,MAAM,WAAW,GAAI,KAAK,CAAC,QAAgC,EAAE,GAAyC,CAAC;IACvG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,WAAW,EAAE,iBAAiB,IAAI,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClG,OAAO,kBAAkB,CAAC;QACzB,OAAO;QACP,OAAO,EAAE,KAAK,CAAC,KAAK;QACpB,UAAU,EAAE,OAAO,CAAC,cAAc;QAClC,MAAM,EAAE,GAAG;KACX,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,MAAc;IACpC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAClF,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,OAAO,GAAG,OAAO,wBAAwB,CAAC;AAC3C,CAAC;AAED,SAAS,SAAS,CAAC,OAA8B,EAAE,QAA4C,EAAE,UAAmB;IACnH,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,IAAK,QAAgC,EAAE,GAAG,EAAE,gBAAgB,IAAI,EAAE,CAAC;IAC9F,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,IAAK,QAAgC,EAAE,GAAG,EAAE,aAAa,IAAI,EAAE,CAAC;IAE7F,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACxD,CAAC;IAED,6EAA6E;IAC7E,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC;IAED,qEAAqE;IACrE,wEAAwE;IACxE,IAAI,CAAC,OAAO,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;YAChC,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,KAAK,WAAW;mBAC3C,GAAG,CAAC,QAAQ,KAAK,WAAW;mBAC5B,GAAG,CAAC,QAAQ,KAAK,SAAS;mBAC1B,GAAG,CAAC,QAAQ,KAAK,KAAK,CAAC;YAE3B,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;YAC5B,IAAI,WAAW,GAAG,KAAK,CAAC;YACxB,IAAI,CAAC,UAAU,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAC1C,QAAQ,GAAG,QAAQ,CAAC;gBACpB,WAAW,GAAG,IAAI,CAAC;YACpB,CAAC;YAED,uDAAuD;YACvD,8DAA8D;YAC9D,2EAA2E;YAC3E,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC7E,MAAM,UAAU,GAAG,CAAC,CAAC,WAAW,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,OAAO,GAAG,GAAG,QAAQ,KAAK,IAAI,GAAG,UAAU,EAAE,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACR,4CAA4C;QAC7C,CAAC;IACF,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,yFAAyF,CAAC,CAAC;IAC5G,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAqB;IAC9C,OAAO;QACN,eAAe,EAAE,IAAI;QACrB,EAAE,EAAE,OAAO,CAAC,GAAG;QACf,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,mEAAmE;QACnE,wCAAwC;QACxC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;QAC1B,aAAa,EAAE,OAAO,CAAC,cAAc,KAAK,IAAI;KAC9C,CAAC;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,KAAmB,EAAE,MAAsB,EAAE,YAAgC,EAAE,GAAmB,EAAE,QAA6B;IAC/J,IAAI,CAAC,YAAY;QAAE,OAAO;IAE1B,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAEzD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;gBACzB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;gBAE/F,gCAAgC;gBAChC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;oBAC1B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,cAAc,CAAC,kBAAkB,CAAC,CAAC;gBACxG,CAAC;gBACD,IAAI,MAAM,CAAC,sBAAsB,EAAE,CAAC;oBACnC,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,sBAAsB,CAAC,CAAC;gBACxE,CAAC;gBAED,kEAAkE;gBAClE,oEAAoE;gBACpE,kEAAkE;gBAClE,+DAA+D;gBAC/D,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;gBAC/E,IAAI,OAAO,EAAE,CAAC;oBACb,MAAM,MAAM,GAAG,KAAK,CAAC,MAAwB,CAAC;oBAC9C,MAAM,CAAC,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;oBACxC,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC;gBAC1C,CAAC;YACF,CAAC;QACF,CAAC;aAAM,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAC5D,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,GAAG,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;AACF,CAAC","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;
|
|
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
|
-
|
|
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":"
|
|
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
|
-
/**
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
167
|
-
// URLs
|
|
168
|
-
// (
|
|
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
|
|
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 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/pages/index.ts\nimport { redirect } from '@sveltejs/kit';\nimport { SentinelClient } from '../../core/client.js';\nimport { setSessionCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';\nimport { getConfigFromPlatform, clearLoginCookies } from '../utils.js';\nimport { validateToken } from '../../core/auth-utils.js';\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 (/^[/\\\\]{2}/.test(path) || /^[a-z][a-z0-9+.-]*:/i.test(path)) return '/';\n\treturn path.startsWith('/') ? path : '/';\n}\n\n/**\n * Detects SvelteKit redirects using ALL THREE methods.\n * CRITICAL: SvelteKit's Redirect object is NOT an instance of Response.\n */\nfunction isRedirectError(error: unknown): boolean {\n\treturn (\n\t\terror instanceof Response ||\n\t\t(error !== null && typeof error === 'object' && (error as any).constructor?.name === 'Redirect') ||\n\t\t(error !== null && typeof error === 'object' && 'status' in error && 'location' in error)\n\t);\n}\n\nexport interface MagicLinkResult {\n\tsuccess: boolean;\n\tcrossBrowser?: boolean;\n}\n\n/**\n * Handles the magic link page load.\n * TWO-STEP process: completePasswordless() → claimTokens()\n * \n * Usage in +page.server.ts:\n * ```typescript\n * import { handleMagicLink } from '@nexartis/sentinel-sdk/sveltekit/pages';\n * \n * export const load: PageServerLoad = async (event) => {\n * return handleMagicLink(event);\n * };\n * ```\n */\nexport async function handleMagicLink(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst magicToken = event.url.searchParams.get('token');\n\n\tif (!magicToken) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\t// Step 1: Complete the passwordless login (marks token as used)\n\t\tconst result = await sentinel.completePasswordless(magicToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\t// completePasswordless() only returns { code, message }\n\t\t\t// It does NOT return tokens - those must be claimed separately\n\n\t\t\tconst transactionId = event.cookies.get('login_transaction_id');\n\t\t\tconst clientToken = event.cookies.get('login_client_token');\n\t\t\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\t\t\t// Check if this is the same browser that initiated the login\n\t\t\tif (!transactionId || !clientToken) {\n\t\t\t\t// Different browser - show success message\n\t\t\t\tthrow redirect(302, '/auth?success=magic_link_clicked');\n\t\t\t}\n\n\t\t\t// Same browser - claim tokens immediately\n\t\t\t// Step 2: Claim the tokens\n\t\t\tconst claimResult = await sentinel.claimTokens(transactionId, clientToken);\n\n\t\t\tif (claimResult.code === 0 || claimResult.success) {\n\t\t\t\tif (claimResult.access_token && claimResult.refresh_token) {\n\t\t\t\t\tconst postAuthRedirect = safeRedirectPath(event.platform?.env?.SENTINEL_POST_AUTH_REDIRECT);\n\t\t\t\t\tsetSessionCookies(event.cookies, claimResult.access_token, claimResult.refresh_token, rememberMe);\n\t\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\t\tthrow redirect(303, postAuthRedirect);\n\t\t\t\t} else {\n\t\t\t\t\tthrow redirect(302, '/auth?error=no_tokens');\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow redirect(302, '/auth?error=claim_failed');\n\t\t\t}\n\t\t} else {\n\t\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\t// CRITICAL: Detect redirects using ALL three methods\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error; // Re-throw immediately\n\t\t}\n\n\t\tconsole.error('Magic link error:', error);\n\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t}\n}\n\n/**\n * Handles the email verification page load.\n * \n * **Breaking change (v1.1):** Now requires `cookies` on the event object\n * (previously only needed `url`, `fetch`, `platform`).\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerifyEmail } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerifyEmail(event);\n * };\n * ```\n */\nexport async function handleVerifyEmail(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst token = event.url.searchParams.get('token');\n\n\tif (!token) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\tconst result = await sentinel.verifyEmail(token);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token && result.refresh_token) {\n\t\t\t\t// SEC-001 fix: Set tokens in short-lived httpOnly cookies (NOT URL params).\n\t\t\t\t// These are consumed immediately by handleVerified on /auth/verified.\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tthrow redirect(303, '/auth/verified');\n\t\t\t} else {\n\t\t\t\t// Success but no tokens — clear any stale verify cookies from prior attempts\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tthrow redirect(302, '/auth?verified=true');\n\t\t\t}\n\t\t} else {\n\t\t\t// Clear any stale verify cookies from prior attempts\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error;\n\t\t}\n\n\t\t// Clear any stale verify cookies on error\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tconsole.error('Email verification error:', error);\n\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t}\n}\n\n/**\n * Handles the verified intermediate page load.\n * Reads tokens from short-lived httpOnly cookies (set by handleVerifyEmail),\n * validates the access token, then establishes the session.\n *\n * **Breaking change (v1.1):** Now requires `fetch` and `platform` on the event\n * object (previously only needed `url` and `cookies`) for server-side token validation.\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerified } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerified(event);\n * };\n * ```\n */\nexport async function handleVerified(event: {\n\turl: URL;\n\tcookies: any;\n\tfetch: typeof globalThis.fetch;\n\tplatform?: any;\n}): Promise<never> {\n\tconst accessToken = event.cookies.get(COOKIE_NAMES.VERIFY_ACCESS_TOKEN);\n\tconst refreshToken = event.cookies.get(COOKIE_NAMES.VERIFY_REFRESH_TOKEN);\n\t// Sanitize redirect target to prevent open-redirect attacks.\n\t// Only allow relative paths starting with a single '/'. Reject protocol-relative\n\t// URLs ('//evil.com'), absolute URLs ('https://evil.com'), backslash variants\n\t// ('/\\evil.com' which some user agents normalise to '//evil.com'), percent-encoded\n\t// backslashes ('%5c' / '%5C' which browsers may decode and normalise the same way),\n\t// and bare strings.\n\tconst rawRedirect = event.url.searchParams.get('redirect') || '/';\n\tconst redirectTo = (rawRedirect.startsWith('/') && !rawRedirect.startsWith('//') && !rawRedirect.includes('\\\\') && !rawRedirect.toLowerCase().includes('%5c')) ? rawRedirect : '/';\n\n\tif (!accessToken || !refreshToken) {\n\t\t// Clear any partial verify cookies to avoid stale state within the TTL\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// SEC-002 fix: Validate the token before consuming the cookies.\n\t// On transient network/API failures, leave cookies intact so the user can\n\t// retry within the 60s TTL without re-running the entire verification flow.\n\tconst config = getConfigFromPlatform(event.platform);\n\tlet decoded;\n\ttry {\n\t\tdecoded = await validateToken(accessToken, event.fetch, config);\n\t} catch {\n\t\t// Transient error — preserve cookies for retry\n\t\tthrow redirect(302, '/auth?error=verification_error');\n\t}\n\tif (!decoded) {\n\t\t// Definitively invalid token — clear cookies, no point retrying\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// Consume single-use verification cookies after successful validation\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\n\t// rememberMe=true is intentional: email verification proves device trust.\n\t// The original registration form's rememberMe preference cannot be reliably\n\t// carried through the async email verification flow. (ARCH-004)\n\tsetSessionCookies(event.cookies, accessToken, refreshToken, true);\n\n\t// Redirect to destination\n\tthrow redirect(303, redirectTo);\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/pages/index.ts\nimport { redirect } from '@sveltejs/kit';\nimport { SentinelClient } from '../../core/client.js';\nimport { setSessionCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';\nimport { getConfigFromPlatform, clearLoginCookies } from '../utils.js';\nimport { validateToken } from '../../core/auth-utils.js';\n\n/**\n * Sanitise a redirect path to prevent open-redirect via env misconfiguration or\n * a crafted `?redirect=` value. Only single-slash relative paths are allowed.\n * Rejects protocol-relative ('//x', '/\\x'), percent-encoded backslashes ('%5c'),\n * and any string carrying whitespace or ASCII control characters — browsers strip\n * tabs/newlines while parsing a URL, so `/\\t/evil.com` would otherwise normalise\n * to `//evil.com` and redirect cross-origin.\n */\nfunction safeRedirectPath(raw: string | undefined): string {\n\tconst path = (raw || '/').trim();\n\tif (!path.startsWith('/')) return '/';\n\tif (/^[/\\\\]{2}/.test(path)) return '/';\n\tif (/[\\s\\u0000-\\u001f\\u007f]/.test(path)) return '/';\n\tif (path.includes('\\\\') || path.toLowerCase().includes('%5c')) return '/';\n\treturn path;\n}\n\n/**\n * Detects SvelteKit redirects using ALL THREE methods.\n * CRITICAL: SvelteKit's Redirect object is NOT an instance of Response.\n */\nfunction isRedirectError(error: unknown): boolean {\n\treturn (\n\t\terror instanceof Response ||\n\t\t(error !== null && typeof error === 'object' && (error as any).constructor?.name === 'Redirect') ||\n\t\t(error !== null && typeof error === 'object' && 'status' in error && 'location' in error)\n\t);\n}\n\nexport interface MagicLinkResult {\n\tsuccess: boolean;\n\tcrossBrowser?: boolean;\n}\n\n/**\n * Handles the magic link page load.\n * TWO-STEP process: completePasswordless() → claimTokens()\n * \n * Usage in +page.server.ts:\n * ```typescript\n * import { handleMagicLink } from '@nexartis/sentinel-sdk/sveltekit/pages';\n * \n * export const load: PageServerLoad = async (event) => {\n * return handleMagicLink(event);\n * };\n * ```\n */\nexport async function handleMagicLink(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst magicToken = event.url.searchParams.get('token');\n\n\tif (!magicToken) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\t// Step 1: Complete the passwordless login (marks token as used)\n\t\tconst result = await sentinel.completePasswordless(magicToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\t// completePasswordless() only returns { code, message }\n\t\t\t// It does NOT return tokens - those must be claimed separately\n\n\t\t\tconst transactionId = event.cookies.get('login_transaction_id');\n\t\t\tconst clientToken = event.cookies.get('login_client_token');\n\t\t\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\t\t\t// Check if this is the same browser that initiated the login\n\t\t\tif (!transactionId || !clientToken) {\n\t\t\t\t// Different browser - show success message\n\t\t\t\tthrow redirect(302, '/auth?success=magic_link_clicked');\n\t\t\t}\n\n\t\t\t// Same browser - claim tokens immediately\n\t\t\t// Step 2: Claim the tokens\n\t\t\tconst claimResult = await sentinel.claimTokens(transactionId, clientToken);\n\n\t\t\tif (claimResult.code === 0 || claimResult.success) {\n\t\t\t\tif (claimResult.access_token && claimResult.refresh_token) {\n\t\t\t\t\tconst postAuthRedirect = safeRedirectPath(event.platform?.env?.SENTINEL_POST_AUTH_REDIRECT);\n\t\t\t\t\tsetSessionCookies(event.cookies, claimResult.access_token, claimResult.refresh_token, rememberMe);\n\t\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\t\tthrow redirect(303, postAuthRedirect);\n\t\t\t\t} else {\n\t\t\t\t\tthrow redirect(302, '/auth?error=no_tokens');\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow redirect(302, '/auth?error=claim_failed');\n\t\t\t}\n\t\t} else {\n\t\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\t// CRITICAL: Detect redirects using ALL three methods\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error; // Re-throw immediately\n\t\t}\n\n\t\tconsole.error('Magic link error:', error);\n\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t}\n}\n\n/**\n * Handles the email verification page load.\n * \n * **Breaking change (v1.1):** Now requires `cookies` on the event object\n * (previously only needed `url`, `fetch`, `platform`).\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerifyEmail } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerifyEmail(event);\n * };\n * ```\n */\nexport async function handleVerifyEmail(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst token = event.url.searchParams.get('token');\n\n\tif (!token) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\tconst result = await sentinel.verifyEmail(token);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token && result.refresh_token) {\n\t\t\t\t// SEC-001 fix: Set tokens in short-lived httpOnly cookies (NOT URL params).\n\t\t\t\t// These are consumed immediately by handleVerified on /auth/verified.\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tthrow redirect(303, '/auth/verified');\n\t\t\t} else {\n\t\t\t\t// Success but no tokens — clear any stale verify cookies from prior attempts\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tthrow redirect(302, '/auth?verified=true');\n\t\t\t}\n\t\t} else {\n\t\t\t// Clear any stale verify cookies from prior attempts\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error;\n\t\t}\n\n\t\t// Clear any stale verify cookies on error\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tconsole.error('Email verification error:', error);\n\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t}\n}\n\n/**\n * Handles the verified intermediate page load.\n * Reads tokens from short-lived httpOnly cookies (set by handleVerifyEmail),\n * validates the access token, then establishes the session.\n *\n * **Breaking change (v1.1):** Now requires `fetch` and `platform` on the event\n * object (previously only needed `url` and `cookies`) for server-side token validation.\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerified } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerified(event);\n * };\n * ```\n */\nexport async function handleVerified(event: {\n\turl: URL;\n\tcookies: any;\n\tfetch: typeof globalThis.fetch;\n\tplatform?: any;\n}): Promise<never> {\n\tconst accessToken = event.cookies.get(COOKIE_NAMES.VERIFY_ACCESS_TOKEN);\n\tconst refreshToken = event.cookies.get(COOKIE_NAMES.VERIFY_REFRESH_TOKEN);\n\t// Sanitize redirect target to prevent open-redirect attacks. Only relative\n\t// paths starting with a single '/' survive; protocol-relative URLs, absolute\n\t// URLs, backslash variants, percent-encoded backslashes, and control/whitespace\n\t// characters (which browsers strip during URL parsing) all fall back to '/'.\n\tconst rawRedirect = event.url.searchParams.get('redirect') || '/';\n\tconst redirectTo = safeRedirectPath(rawRedirect);\n\n\tif (!accessToken || !refreshToken) {\n\t\t// Clear any partial verify cookies to avoid stale state within the TTL\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// SEC-002 fix: Validate the token before consuming the cookies.\n\t// On transient network/API failures, leave cookies intact so the user can\n\t// retry within the 60s TTL without re-running the entire verification flow.\n\tconst config = getConfigFromPlatform(event.platform);\n\tlet decoded;\n\ttry {\n\t\tdecoded = await validateToken(accessToken, event.fetch, config);\n\t} catch {\n\t\t// Transient error — preserve cookies for retry\n\t\tthrow redirect(302, '/auth?error=verification_error');\n\t}\n\tif (!decoded) {\n\t\t// Definitively invalid token — clear cookies, no point retrying\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// Consume single-use verification cookies after successful validation\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\n\t// rememberMe=true is intentional: email verification proves device trust.\n\t// The original registration form's rememberMe preference cannot be reliably\n\t// carried through the async email verification flow. (ARCH-004)\n\tsetSessionCookies(event.cookies, accessToken, refreshToken, true);\n\n\t// Redirect to destination\n\tthrow redirect(303, redirectTo);\n}\n\n"]}
|