@nexartis/sentinel-sdk 1.6.0 → 1.8.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.
@@ -37,6 +37,12 @@ export interface StartLoginResponse extends SentinelResponseBase {
37
37
  /** Response from POST /auth/poll */
38
38
  export interface PollResponse extends SentinelResponseBase {
39
39
  status?: 'pending' | 'completed' | 'expired';
40
+ /**
41
+ * Opaque transaction-bound redirect hint echoed back on the claimed poll
42
+ * response. Present only when the originating register/startLogin call
43
+ * supplied a `redirectContext`. Null or absent otherwise.
44
+ */
45
+ redirect_context?: string | null;
40
46
  }
41
47
  /** Response from POST /auth/claim */
42
48
  export interface ClaimResponse extends SentinelResponseBase {
@@ -80,11 +86,23 @@ export interface RegisterResponse extends SentinelResponseBase {
80
86
  export interface VerifyResponse extends SentinelResponseBase {
81
87
  access_token?: string;
82
88
  refresh_token?: string;
89
+ /**
90
+ * Opaque transaction-bound redirect hint echoed back from the originating
91
+ * register call. Present only when a `redirectContext` was supplied. Null
92
+ * or absent otherwise.
93
+ */
94
+ redirect_context?: string | null;
83
95
  }
84
96
  /** Response from POST /auth/complete-passwordless */
85
97
  export interface CompletePasswordlessResponse extends SentinelResponseBase {
86
98
  access_token?: string;
87
99
  refresh_token?: string;
100
+ /**
101
+ * Opaque transaction-bound redirect hint echoed back from the originating
102
+ * startLogin call. Present only when a `redirectContext` was supplied. Null
103
+ * or absent otherwise.
104
+ */
105
+ redirect_context?: string | null;
88
106
  }
89
107
  /**
90
108
  * @deprecated Use the specific typed response interfaces instead.
@@ -117,6 +135,21 @@ export interface SentinelHandleOptions {
117
135
  baseUrl?: string;
118
136
  /** Optional structured logger for auth middleware. */
119
137
  logger?: SentinelLogger;
138
+ /**
139
+ * Full JWKS URL used to verify access-token signatures.
140
+ * Default: derived from `apiUrl` (origin + `/.well-known/jwks.json`).
141
+ * Also overridable via the `SENTINEL_JWKS_URL` environment variable.
142
+ */
143
+ jwksUrl?: string;
144
+ /** JWKS cache TTL in milliseconds. Default: 600_000 (10 minutes). */
145
+ jwksCacheTtlMs?: number;
146
+ /**
147
+ * DEVELOPMENT ONLY: disable JWKS signature verification and fall back to
148
+ * decoding the (unverified) token payload locally. Equivalent to setting
149
+ * the environment variable `SENTINEL_SKIP_JWKS_VERIFY=1`. Never enable
150
+ * this in production — the admin gate would again trust unverified claims.
151
+ */
152
+ skipJwksVerify?: boolean;
120
153
  }
121
154
  /** Data passed to the onBeforeLogin hook. */
122
155
  export interface BeforeLoginContext {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["// src/core/types.ts\nimport type { SentinelLogger } from './logger.js';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface SentinelConfig {\n\tapiUrl: string;\n\tbaseUrl: string;\n\t/**\n\t * When true, validateToken() performs a second GET /users/me request after\n\t * POST /auth/validate to refresh DB-backed profile fields such as roles.\n\t * Default: false. Keep disabled for high-volume per-request validation paths.\n\t */\n\thydrateUserProfile?: boolean;\n\t/**\n\t * Timeout in milliseconds for each individual fetch call to the Sentinel API.\n\t * Default: 8000 (8 seconds).\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Maximum number of retries for retryable errors (network timeout, HTTP 502/503/504).\n\t * Non-retryable errors (400, 401, 403) are never retried.\n\t * Default: 1.\n\t */\n\tmaxRetries?: number;\n\t/** Optional structured logger. Falls back to console if not provided. */\n\tlogger?: SentinelLogger;\n}\n\n// ---------------------------------------------------------------------------\n// Typed API Responses (replaces the old untyped SentinelResponse bag)\n// ---------------------------------------------------------------------------\n\n/** Base fields shared by all Sentinel API responses. */\nexport interface SentinelResponseBase {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n}\n\n/** Response from POST /auth/login-passwordless */\nexport interface StartLoginResponse extends SentinelResponseBase {\n\ttransactionId?: string;\n\tclientToken?: string;\n}\n\n/** Response from POST /auth/poll */\nexport interface PollResponse extends SentinelResponseBase {\n\tstatus?: 'pending' | 'completed' | 'expired';\n}\n\n/** Response from POST /auth/claim */\nexport interface ClaimResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/refresh */\nexport interface RefreshResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/validate */\nexport interface ValidateResponse extends SentinelResponseBase {\n\tvalid?: boolean;\n\tpayload?: DecodedToken;\n\tuser?: {\n\t\tsub: string;\n\t\temail: string;\n\t\troles: string[];\n\t};\n}\n\n/** Response from GET /users/me. */\nexport interface CurrentUserResponse extends SentinelResponseBase {\n\tid?: string;\n\temail?: string;\n\trole?: string;\n\troles?: string[];\n\tstatus?: string;\n\tlegalName?: string;\n\tocmeMemberNumber?: string;\n\twalletAddress?: string;\n\taliasName?: string;\n\ttwitterAccount?: string;\n\tyoutubeAccount?: string;\n\tcubicubeAddress?: string;\n}\n\n/** Response from POST /auth/register */\nexport interface RegisterResponse extends SentinelResponseBase {}\n\n/** Response from POST /auth/verify */\nexport interface VerifyResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/complete-passwordless */\nexport interface CompletePasswordlessResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/**\n * @deprecated Use the specific typed response interfaces instead.\n * Kept for backward compatibility — will be removed in v2.0.0.\n */\nexport interface SentinelResponse {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n\t[key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// JWT / Auth\n// ---------------------------------------------------------------------------\n\nexport interface DecodedToken {\n\tsub: string;\n\temail: string;\n\troles: string[];\n\texp: number;\n\tiat: number;\n}\n\nexport interface UserLocals {\n\tisAuthenticated: boolean;\n\tid: string;\n\temail: string;\n\troles: string[];\n}\n\n// ---------------------------------------------------------------------------\n// SvelteKit Handle Options\n// ---------------------------------------------------------------------------\n\nexport interface SentinelHandleOptions {\n\tprotectedRoutes?: string[];\n\tloginRedirect?: string;\n\tapiUrl?: string;\n\tbaseUrl?: string;\n\t/** Optional structured logger for auth middleware. */\n\tlogger?: SentinelLogger;\n}\n\n// ---------------------------------------------------------------------------\n// Auth Handler Options & Lifecycle Hooks\n// ---------------------------------------------------------------------------\n\n/** Data passed to the onBeforeLogin hook. */\nexport interface BeforeLoginContext {\n\temail: string;\n\trememberMe: boolean;\n\tredirectTo?: string;\n}\n\n/**\n * Return this from onBeforeLogin to reject the request.\n *\n * When `redirect` is set and is a safe relative path (starts with `/`, no\n * protocol, no `//` / `\\\\` prefix), the SDK issues a `303 See Other` to that\n * path instead of returning the JSON error body. Unsafe redirects are ignored\n * and the JSON-reject path is used as a fallback.\n *\n * @example\n * ```ts\n * onBeforeLogin: async (_event, { email }) => {\n * const gate = await isLoginAllowed(email);\n * if (!gate.ok) {\n * return {\n * error: gate.reason,\n * status: 403,\n * redirect: gate.reason === 'waitlisted' ? '/waitlist' : '/unauthorized'\n * };\n * }\n * }\n * ```\n */\nexport interface BeforeLoginReject {\n\terror: string;\n\tstatus?: number;\n\t/**\n\t * Optional branded destination the SDK will 303-redirect to instead of\n\t * returning a JSON error. Must be a safe relative path (starts with `/`,\n\t * no protocol, no `//` / `\\\\` prefix). Unsafe values fall back to the\n\t * JSON-reject behaviour.\n\t */\n\tredirect?: string;\n}\n\n/** Data passed to the onAfterLogin hook. */\nexport interface AfterLoginContext {\n\temail: string;\n\ttransactionId: string;\n}\n\n/** Data passed to the onAfterClaim hook. */\nexport interface AfterClaimContext {\n\taccessToken: string;\n\trefreshToken: string;\n\tredirectTo?: string;\n}\n\n/** Data passed to the onError hook. */\nexport interface ErrorContext {\n\thandler: 'start-login' | 'poll' | 'claim' | 'logout' | 'refresh' | 'register' | 'verify';\n\terror: unknown;\n}\n\n/**\n * Context passed to the `resolveRedirect` hook after a successful token claim.\n * Hosts use this to pick a post-login destination based on the user's roles,\n * a requested redirect, or fall back to the configured default.\n */\nexport interface ResolveRedirectContext {\n\t/** Newly authenticated user, decoded from the access token. */\n\tuser: UserLocals;\n\t/** Access token just issued for this user. */\n\taccessToken: string;\n\t/** Whether the user opted into a longer session at login time. */\n\trememberMe: boolean;\n\t/**\n\t * The redirect the client originally requested (from the `login_redirect_to`\n\t * cookie, `?redirect=` on `/auth`, or the `redirectTo` body field).\n\t * Already sanitised to a safe relative path by the SDK.\n\t */\n\trequestedRedirect?: string;\n\t/** The redirect that would be used if the resolver is absent. Always safe. */\n\tdefaultRedirect: string;\n}\n\n/**\n * Context passed to the `logoutRedirect` function form, if the host wants to\n * decide the post-logout landing at request time (e.g. send authenticated\n * users to `/` and anonymous users to `/auth`).\n */\nexport interface LogoutRedirectContext {\n\t/** User present when logout was invoked, if any (may be null for stale sessions). */\n\tuser: UserLocals | null;\n}\n\nexport interface AuthHandlerOptions {\n\t/**\n\t * Login cookie TTL in seconds. Controls how long the login transaction\n\t * cookies (transaction ID, client token, remember-me) persist.\n\t * Default: 300 (5 minutes).\n\t */\n\tloginCookieTtlSeconds?: number;\n\n\t/**\n\t * Path to redirect unauthenticated users (logout, claim errors).\n\t * Default: '/auth'.\n\t */\n\tloginRedirect?: string;\n\n\t/**\n\t * Store a `login_redirect_to` cookie from the request body's `redirectTo`\n\t * field, then use it on claim to redirect the user back to their original page.\n\t * Default: true.\n\t */\n\tredirectCookie?: boolean;\n\n\t/** Optional structured logger for auth handlers. */\n\tlogger?: SentinelLogger;\n\n\t/**\n\t * Runs before the startLogin API call.\n\t * Return `{ error, status? }` to reject the request.\n\t */\n\tonBeforeLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: BeforeLoginContext) => Promise<BeforeLoginReject | void> | BeforeLoginReject | void;\n\n\t/** Runs after a successful startLogin (transaction cookies set). */\n\tonAfterLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterLoginContext) => Promise<void> | void;\n\n\t/** Runs after successful token claim (session cookies set). */\n\tonAfterClaim?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterClaimContext) => Promise<void> | void;\n\n\t/** Runs on any handler error. */\n\tonError?: (event: import('@sveltejs/kit').RequestEvent, ctx: ErrorContext) => Promise<void> | void;\n\n\t/**\n\t * Called after a successful token claim to decide the post-login destination.\n\t * Return a safe relative path (starts with `/`, no protocol, no `//` prefix).\n\t * If the resolver throws, returns a non-string, an empty string, or an unsafe\n\t * path, the SDK falls back to `ctx.defaultRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * resolveRedirect: async ({ user, requestedRedirect, defaultRedirect }) => {\n\t * if (user.roles.includes('admin')) return '/admin';\n\t * return requestedRedirect || defaultRedirect;\n\t * }\n\t * ```\n\t */\n\tresolveRedirect?: (\n\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\tctx: ResolveRedirectContext\n\t) => Promise<string> | string;\n\n\t/**\n\t * Where `POST /logout` and `GET /logout` redirect after clearing cookies.\n\t *\n\t * - Omitted: falls back to `loginRedirect` (current behaviour, `/auth` by default).\n\t * - String: treated as a literal safe relative path. Unsafe values fall back to `loginRedirect`.\n\t * - Function: called with the pre-logout user context; return a safe relative\n\t * path (async permitted). Throws / empty / unsafe → falls back to `loginRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * logoutRedirect: '/'\n\t * // or\n\t * logoutRedirect: ({ user }) => user ? '/' : '/auth'\n\t * ```\n\t */\n\tlogoutRedirect?:\n\t\t| string\n\t\t| ((\n\t\t\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\t\t\tctx: LogoutRedirectContext\n\t\t ) => Promise<string> | string);\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["// src/core/types.ts\nimport type { SentinelLogger } from './logger.js';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface SentinelConfig {\n\tapiUrl: string;\n\tbaseUrl: string;\n\t/**\n\t * When true, validateToken() performs a second GET /users/me request after\n\t * POST /auth/validate to refresh DB-backed profile fields such as roles.\n\t * Default: false. Keep disabled for high-volume per-request validation paths.\n\t */\n\thydrateUserProfile?: boolean;\n\t/**\n\t * Timeout in milliseconds for each individual fetch call to the Sentinel API.\n\t * Default: 8000 (8 seconds).\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Maximum number of retries for retryable errors (network timeout, HTTP 502/503/504).\n\t * Non-retryable errors (400, 401, 403) are never retried.\n\t * Default: 1.\n\t */\n\tmaxRetries?: number;\n\t/** Optional structured logger. Falls back to console if not provided. */\n\tlogger?: SentinelLogger;\n}\n\n// ---------------------------------------------------------------------------\n// Typed API Responses (replaces the old untyped SentinelResponse bag)\n// ---------------------------------------------------------------------------\n\n/** Base fields shared by all Sentinel API responses. */\nexport interface SentinelResponseBase {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n}\n\n/** Response from POST /auth/login-passwordless */\nexport interface StartLoginResponse extends SentinelResponseBase {\n\ttransactionId?: string;\n\tclientToken?: string;\n}\n\n/** Response from POST /auth/poll */\nexport interface PollResponse extends SentinelResponseBase {\n\tstatus?: 'pending' | 'completed' | 'expired';\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back on the claimed poll\n\t * response. Present only when the originating register/startLogin call\n\t * supplied a `redirectContext`. Null or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/** Response from POST /auth/claim */\nexport interface ClaimResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/refresh */\nexport interface RefreshResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/validate */\nexport interface ValidateResponse extends SentinelResponseBase {\n\tvalid?: boolean;\n\tpayload?: DecodedToken;\n\tuser?: {\n\t\tsub: string;\n\t\temail: string;\n\t\troles: string[];\n\t};\n}\n\n/** Response from GET /users/me. */\nexport interface CurrentUserResponse extends SentinelResponseBase {\n\tid?: string;\n\temail?: string;\n\trole?: string;\n\troles?: string[];\n\tstatus?: string;\n\tlegalName?: string;\n\tocmeMemberNumber?: string;\n\twalletAddress?: string;\n\taliasName?: string;\n\ttwitterAccount?: string;\n\tyoutubeAccount?: string;\n\tcubicubeAddress?: string;\n}\n\n/** Response from POST /auth/register */\nexport interface RegisterResponse extends SentinelResponseBase {}\n\n/** Response from POST /auth/verify */\nexport interface VerifyResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back from the originating\n\t * register call. Present only when a `redirectContext` was supplied. Null\n\t * or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/** Response from POST /auth/complete-passwordless */\nexport interface CompletePasswordlessResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back from the originating\n\t * startLogin call. Present only when a `redirectContext` was supplied. Null\n\t * or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/**\n * @deprecated Use the specific typed response interfaces instead.\n * Kept for backward compatibility — will be removed in v2.0.0.\n */\nexport interface SentinelResponse {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n\t[key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// JWT / Auth\n// ---------------------------------------------------------------------------\n\nexport interface DecodedToken {\n\tsub: string;\n\temail: string;\n\troles: string[];\n\texp: number;\n\tiat: number;\n}\n\nexport interface UserLocals {\n\tisAuthenticated: boolean;\n\tid: string;\n\temail: string;\n\troles: string[];\n}\n\n// ---------------------------------------------------------------------------\n// SvelteKit Handle Options\n// ---------------------------------------------------------------------------\n\nexport interface SentinelHandleOptions {\n\tprotectedRoutes?: string[];\n\tloginRedirect?: string;\n\tapiUrl?: string;\n\tbaseUrl?: string;\n\t/** Optional structured logger for auth middleware. */\n\tlogger?: SentinelLogger;\n\t/**\n\t * Full JWKS URL used to verify access-token signatures.\n\t * Default: derived from `apiUrl` (origin + `/.well-known/jwks.json`).\n\t * Also overridable via the `SENTINEL_JWKS_URL` environment variable.\n\t */\n\tjwksUrl?: string;\n\t/** JWKS cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tjwksCacheTtlMs?: number;\n\t/**\n\t * DEVELOPMENT ONLY: disable JWKS signature verification and fall back to\n\t * decoding the (unverified) token payload locally. Equivalent to setting\n\t * the environment variable `SENTINEL_SKIP_JWKS_VERIFY=1`. Never enable\n\t * this in production — the admin gate would again trust unverified claims.\n\t */\n\tskipJwksVerify?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Auth Handler Options & Lifecycle Hooks\n// ---------------------------------------------------------------------------\n\n/** Data passed to the onBeforeLogin hook. */\nexport interface BeforeLoginContext {\n\temail: string;\n\trememberMe: boolean;\n\tredirectTo?: string;\n}\n\n/**\n * Return this from onBeforeLogin to reject the request.\n *\n * When `redirect` is set and is a safe relative path (starts with `/`, no\n * protocol, no `//` / `\\\\` prefix), the SDK issues a `303 See Other` to that\n * path instead of returning the JSON error body. Unsafe redirects are ignored\n * and the JSON-reject path is used as a fallback.\n *\n * @example\n * ```ts\n * onBeforeLogin: async (_event, { email }) => {\n * const gate = await isLoginAllowed(email);\n * if (!gate.ok) {\n * return {\n * error: gate.reason,\n * status: 403,\n * redirect: gate.reason === 'waitlisted' ? '/waitlist' : '/unauthorized'\n * };\n * }\n * }\n * ```\n */\nexport interface BeforeLoginReject {\n\terror: string;\n\tstatus?: number;\n\t/**\n\t * Optional branded destination the SDK will 303-redirect to instead of\n\t * returning a JSON error. Must be a safe relative path (starts with `/`,\n\t * no protocol, no `//` / `\\\\` prefix). Unsafe values fall back to the\n\t * JSON-reject behaviour.\n\t */\n\tredirect?: string;\n}\n\n/** Data passed to the onAfterLogin hook. */\nexport interface AfterLoginContext {\n\temail: string;\n\ttransactionId: string;\n}\n\n/** Data passed to the onAfterClaim hook. */\nexport interface AfterClaimContext {\n\taccessToken: string;\n\trefreshToken: string;\n\tredirectTo?: string;\n}\n\n/** Data passed to the onError hook. */\nexport interface ErrorContext {\n\thandler: 'start-login' | 'poll' | 'claim' | 'logout' | 'refresh' | 'register' | 'verify';\n\terror: unknown;\n}\n\n/**\n * Context passed to the `resolveRedirect` hook after a successful token claim.\n * Hosts use this to pick a post-login destination based on the user's roles,\n * a requested redirect, or fall back to the configured default.\n */\nexport interface ResolveRedirectContext {\n\t/** Newly authenticated user, decoded from the access token. */\n\tuser: UserLocals;\n\t/** Access token just issued for this user. */\n\taccessToken: string;\n\t/** Whether the user opted into a longer session at login time. */\n\trememberMe: boolean;\n\t/**\n\t * The redirect the client originally requested (from the `login_redirect_to`\n\t * cookie, `?redirect=` on `/auth`, or the `redirectTo` body field).\n\t * Already sanitised to a safe relative path by the SDK.\n\t */\n\trequestedRedirect?: string;\n\t/** The redirect that would be used if the resolver is absent. Always safe. */\n\tdefaultRedirect: string;\n}\n\n/**\n * Context passed to the `logoutRedirect` function form, if the host wants to\n * decide the post-logout landing at request time (e.g. send authenticated\n * users to `/` and anonymous users to `/auth`).\n */\nexport interface LogoutRedirectContext {\n\t/** User present when logout was invoked, if any (may be null for stale sessions). */\n\tuser: UserLocals | null;\n}\n\nexport interface AuthHandlerOptions {\n\t/**\n\t * Login cookie TTL in seconds. Controls how long the login transaction\n\t * cookies (transaction ID, client token, remember-me) persist.\n\t * Default: 300 (5 minutes).\n\t */\n\tloginCookieTtlSeconds?: number;\n\n\t/**\n\t * Path to redirect unauthenticated users (logout, claim errors).\n\t * Default: '/auth'.\n\t */\n\tloginRedirect?: string;\n\n\t/**\n\t * Store a `login_redirect_to` cookie from the request body's `redirectTo`\n\t * field, then use it on claim to redirect the user back to their original page.\n\t * Default: true.\n\t */\n\tredirectCookie?: boolean;\n\n\t/** Optional structured logger for auth handlers. */\n\tlogger?: SentinelLogger;\n\n\t/**\n\t * Runs before the startLogin API call.\n\t * Return `{ error, status? }` to reject the request.\n\t */\n\tonBeforeLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: BeforeLoginContext) => Promise<BeforeLoginReject | void> | BeforeLoginReject | void;\n\n\t/** Runs after a successful startLogin (transaction cookies set). */\n\tonAfterLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterLoginContext) => Promise<void> | void;\n\n\t/** Runs after successful token claim (session cookies set). */\n\tonAfterClaim?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterClaimContext) => Promise<void> | void;\n\n\t/** Runs on any handler error. */\n\tonError?: (event: import('@sveltejs/kit').RequestEvent, ctx: ErrorContext) => Promise<void> | void;\n\n\t/**\n\t * Called after a successful token claim to decide the post-login destination.\n\t * Return a safe relative path (starts with `/`, no protocol, no `//` prefix).\n\t * If the resolver throws, returns a non-string, an empty string, or an unsafe\n\t * path, the SDK falls back to `ctx.defaultRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * resolveRedirect: async ({ user, requestedRedirect, defaultRedirect }) => {\n\t * if (user.roles.includes('admin')) return '/admin';\n\t * return requestedRedirect || defaultRedirect;\n\t * }\n\t * ```\n\t */\n\tresolveRedirect?: (\n\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\tctx: ResolveRedirectContext\n\t) => Promise<string> | string;\n\n\t/**\n\t * Where `POST /logout` and `GET /logout` redirect after clearing cookies.\n\t *\n\t * - Omitted: falls back to `loginRedirect` (current behaviour, `/auth` by default).\n\t * - String: treated as a literal safe relative path. Unsafe values fall back to `loginRedirect`.\n\t * - Function: called with the pre-logout user context; return a safe relative\n\t * path (async permitted). Throws / empty / unsafe → falls back to `loginRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * logoutRedirect: '/'\n\t * // or\n\t * logoutRedirect: ({ user }) => user ? '/' : '/auth'\n\t * ```\n\t */\n\tlogoutRedirect?:\n\t\t| string\n\t\t| ((\n\t\t\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\t\t\tctx: LogoutRedirectContext\n\t\t ) => Promise<string> | string);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/client/index.ts"],"names":[],"mappings":"AAkDA;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACpD;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAgCD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC9B,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,QAAQ,CAAC,CAqCnB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,GAAG,GAAG,EAC1C,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,CAAC,CAAC,CAQZ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/client/index.ts"],"names":[],"mappings":"AAwEA;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACpD;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAgCD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC9B,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,QAAQ,CAAC,CAqCnB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,CAAC,GAAG,GAAG,EAC1C,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,CAAC,CAAC,CAQZ"}
@@ -9,21 +9,43 @@
9
9
  */
10
10
  let isRefreshing = false;
11
11
  let refreshPromise = null;
12
- async function refreshAccessToken() {
12
+ function composeSignals(signals) {
13
+ const live = signals.filter((s) => s);
14
+ if (live.length === 0) {
15
+ return new AbortController().signal;
16
+ }
17
+ if (live.length === 1) {
18
+ return live[0];
19
+ }
20
+ if (typeof AbortSignal.any === 'function') {
21
+ return AbortSignal.any(live);
22
+ }
23
+ const controller = new AbortController();
24
+ const onAbort = () => {
25
+ const reason = live.find((s) => s.aborted)?.reason;
26
+ if (!controller.signal.aborted)
27
+ controller.abort(reason);
28
+ };
29
+ for (const signal of live) {
30
+ if (signal.aborted) {
31
+ onAbort();
32
+ return controller.signal;
33
+ }
34
+ signal.addEventListener('abort', onAbort, { once: true });
35
+ }
36
+ return controller.signal;
37
+ }
38
+ async function refreshAccessToken(callerSignal) {
13
39
  if (isRefreshing && refreshPromise) {
14
40
  return refreshPromise;
15
41
  }
16
42
  isRefreshing = true;
17
43
  refreshPromise = (async () => {
18
44
  try {
19
- // The refresh sub-call is intentionally NOT wrapped in the caller's
20
- // timeoutMs. It has its own well-behaved backend contract and we do
21
- // not want a mid-refresh abort to leave the client with partially
22
- // rotated tokens. If refresh itself hangs pathologically the
23
- // browser/network stack will eventually fail it.
24
45
  const response = await fetch('/api/auth/refresh', {
25
46
  method: 'POST',
26
- credentials: 'include'
47
+ credentials: 'include',
48
+ signal: callerSignal ?? undefined
27
49
  });
28
50
  if (response.ok) {
29
51
  const result = await response.json();
@@ -65,7 +87,7 @@ function buildAttemptSignal(callerSignal, timeoutMs) {
65
87
  if (!callerSignal) {
66
88
  return timeoutSignal;
67
89
  }
68
- return AbortSignal.any([callerSignal, timeoutSignal]);
90
+ return composeSignals([callerSignal, timeoutSignal]);
69
91
  }
70
92
  /**
71
93
  * Authenticated fetch wrapper with automatic token refresh.
@@ -84,7 +106,7 @@ export async function authFetch(url, options) {
84
106
  signal: buildAttemptSignal(callerSignal, effectiveTimeout)
85
107
  });
86
108
  if (response.status === 401) {
87
- const refreshed = await refreshAccessToken();
109
+ const refreshed = await refreshAccessToken(callerSignal);
88
110
  if (refreshed) {
89
111
  // Attempt #2 — brand new timeout budget. Do NOT reuse attempt #1's
90
112
  // signal (that budget may already be nearly exhausted); build a
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/client/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/client/index.ts\n\n/**\n * Authenticated fetch wrapper with automatic token refresh.\n *\n * Use for:\n * - Multipart/chunked file uploads\n * - Sequential API calls with delays\n * - Any scenario where token might expire between requests\n */\n\nlet isRefreshing = false;\nlet refreshPromise: Promise<boolean> | null = null;\n\nasync function refreshAccessToken(): Promise<boolean> {\n\tif (isRefreshing && refreshPromise) {\n\t\treturn refreshPromise;\n\t}\n\n\tisRefreshing = true;\n\trefreshPromise = (async () => {\n\t\ttry {\n\t\t\t// The refresh sub-call is intentionally NOT wrapped in the caller's\n\t\t\t// timeoutMs. It has its own well-behaved backend contract and we do\n\t\t\t// not want a mid-refresh abort to leave the client with partially\n\t\t\t// rotated tokens. If refresh itself hangs pathologically the\n\t\t\t// browser/network stack will eventually fail it.\n\t\t\tconst response = await fetch('/api/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tcredentials: 'include'\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = await response.json();\n\t\t\t\treturn result.success === true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (error) {\n\t\t\tconsole.error('Token refresh error:', error);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tisRefreshing = false;\n\t\t\trefreshPromise = null;\n\t\t}\n\t})();\n\n\treturn refreshPromise;\n}\n\n/**\n * Extended options for {@link authFetch} / {@link authFetchJson}.\n *\n * Adds a per-attempt request timeout on top of the standard `RequestInit`.\n * The timeout is applied INDEPENDENTLY to the initial request and to the\n * post-401-refresh retry, so a slow first attempt does not steal budget from\n * the retry.\n */\nexport interface AuthFetchOptions extends RequestInit {\n\t/**\n\t * Per-attempt timeout in milliseconds.\n\t *\n\t * - Defaults to `60_000` (60 s) when omitted.\n\t * - `0` or `Infinity` disables the internal timeout entirely (caller's\n\t * own `signal` still applies).\n\t * - The timeout is applied SEPARATELY to attempt #1 and to the post-401\n\t * refresh retry — each gets a fresh budget.\n\t * - Composed with `options.signal` via `AbortSignal.any([...])` so caller\n\t * cancellation still works and a tighter caller signal wins.\n\t * - Does NOT apply to the internal `/api/auth/refresh` sub-call.\n\t */\n\ttimeoutMs?: number;\n}\n\n/** Default per-attempt timeout when `timeoutMs` is not provided. */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/**\n * Build the effective per-attempt AbortSignal for a single fetch attempt.\n *\n * - When `timeoutMs` disables the timeout, we pass through the caller signal\n * (or `undefined`).\n * - When timeout is enabled and the caller supplied a signal, we compose them\n * with `AbortSignal.any` so whichever aborts first wins.\n *\n * Node engine is >=20 so `AbortSignal.any` / `AbortSignal.timeout` are\n * available; modern browsers (Chrome 116+, Firefox 124+, Safari 17.4+)\n * also ship them.\n */\nfunction buildAttemptSignal(\n\tcallerSignal: AbortSignal | null | undefined,\n\ttimeoutMs: number\n): AbortSignal | undefined {\n\tconst timeoutDisabled = timeoutMs === 0 || !Number.isFinite(timeoutMs);\n\tif (timeoutDisabled) {\n\t\treturn callerSignal ?? undefined;\n\t}\n\tconst timeoutSignal = AbortSignal.timeout(timeoutMs);\n\tif (!callerSignal) {\n\t\treturn timeoutSignal;\n\t}\n\treturn AbortSignal.any([callerSignal, timeoutSignal]);\n}\n\n/**\n * Authenticated fetch wrapper with automatic token refresh.\n *\n * Applies a per-attempt timeout (default 60 s) to attempt #1 and to the\n * post-401-refresh retry. Each attempt gets its OWN fresh timeout budget so\n * a slow first attempt cannot starve the retry. See {@link AuthFetchOptions}.\n */\nexport async function authFetch(\n\turl: string | URL,\n\toptions?: AuthFetchOptions\n): Promise<Response> {\n\tconst { timeoutMs, signal: callerSignal, ...restOptions } = options ?? {};\n\tconst effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n\t// Attempt #1 — fresh timeout budget composed with caller signal.\n\tlet response = await fetch(url, {\n\t\t...restOptions,\n\t\tcredentials: 'include',\n\t\tsignal: buildAttemptSignal(callerSignal, effectiveTimeout)\n\t});\n\n\tif (response.status === 401) {\n\t\tconst refreshed = await refreshAccessToken();\n\n\t\tif (refreshed) {\n\t\t\t// Attempt #2 — brand new timeout budget. Do NOT reuse attempt #1's\n\t\t\t// signal (that budget may already be nearly exhausted); build a\n\t\t\t// fresh one instead. The caller's signal is still honored via\n\t\t\t// AbortSignal.any so external cancellation continues to work.\n\t\t\tresponse = await fetch(url, {\n\t\t\t\t...restOptions,\n\t\t\t\tcredentials: 'include',\n\t\t\t\tsignal: buildAttemptSignal(callerSignal, effectiveTimeout)\n\t\t\t});\n\t\t}\n\t\t// If refresh failed, do NOT hard-redirect to /auth.\n\t\t// With Sentinel's rotating refresh tokens, concurrent requests can\n\t\t// race: one wins the refresh (R1→R2) while the others fail because\n\t\t// R1 is already invalidated. A hard redirect would log the user out\n\t\t// even though the browser already has fresh cookies from the winning\n\t\t// response. Instead, return the original 401 response and let the\n\t\t// caller handle it — polling callers will silently retry on the next\n\t\t// interval with the updated cookies, and SvelteKit's protected-route\n\t\t// redirect handles truly expired sessions.\n\t}\n\n\treturn response;\n}\n\n/**\n * Authenticated fetch wrapper with JSON response parsing\n */\nexport async function authFetchJson<T = any>(\n\turl: string | URL,\n\toptions?: AuthFetchOptions\n): Promise<T> {\n\tconst response = await authFetch(url, options);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP error! status: ${response.status}`);\n\t}\n\n\treturn response.json();\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/client/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/client/index.ts\n\n/**\n * Authenticated fetch wrapper with automatic token refresh.\n *\n * Use for:\n * - Multipart/chunked file uploads\n * - Sequential API calls with delays\n * - Any scenario where token might expire between requests\n */\n\nlet isRefreshing = false;\nlet refreshPromise: Promise<boolean> | null = null;\n\nfunction composeSignals(signals: AbortSignal[]): AbortSignal {\n\tconst live = signals.filter((s) => s);\n\tif (live.length === 0) {\n\t\treturn new AbortController().signal;\n\t}\n\tif (live.length === 1) {\n\t\treturn live[0];\n\t}\n\tif (typeof AbortSignal.any === 'function') {\n\t\treturn AbortSignal.any(live);\n\t}\n\tconst controller = new AbortController();\n\tconst onAbort = () => {\n\t\tconst reason = live.find((s) => s.aborted)?.reason;\n\t\tif (!controller.signal.aborted) controller.abort(reason);\n\t};\n\tfor (const signal of live) {\n\t\tif (signal.aborted) {\n\t\t\tonAbort();\n\t\t\treturn controller.signal;\n\t\t}\n\t\tsignal.addEventListener('abort', onAbort, { once: true });\n\t}\n\treturn controller.signal;\n}\n\nasync function refreshAccessToken(callerSignal?: AbortSignal | null): Promise<boolean> {\n\tif (isRefreshing && refreshPromise) {\n\t\treturn refreshPromise;\n\t}\n\n\tisRefreshing = true;\n\trefreshPromise = (async () => {\n\t\ttry {\n\t\t\tconst response = await fetch('/api/auth/refresh', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tcredentials: 'include',\n\t\t\t\tsignal: callerSignal ?? undefined\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = await response.json();\n\t\t\t\treturn result.success === true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (error) {\n\t\t\tconsole.error('Token refresh error:', error);\n\t\t\treturn false;\n\t\t} finally {\n\t\t\tisRefreshing = false;\n\t\t\trefreshPromise = null;\n\t\t}\n\t})();\n\n\treturn refreshPromise;\n}\n\n/**\n * Extended options for {@link authFetch} / {@link authFetchJson}.\n *\n * Adds a per-attempt request timeout on top of the standard `RequestInit`.\n * The timeout is applied INDEPENDENTLY to the initial request and to the\n * post-401-refresh retry, so a slow first attempt does not steal budget from\n * the retry.\n */\nexport interface AuthFetchOptions extends RequestInit {\n\t/**\n\t * Per-attempt timeout in milliseconds.\n\t *\n\t * - Defaults to `60_000` (60 s) when omitted.\n\t * - `0` or `Infinity` disables the internal timeout entirely (caller's\n\t * own `signal` still applies).\n\t * - The timeout is applied SEPARATELY to attempt #1 and to the post-401\n\t * refresh retry — each gets a fresh budget.\n\t * - Composed with `options.signal` via `AbortSignal.any([...])` so caller\n\t * cancellation still works and a tighter caller signal wins.\n\t * - Does NOT apply to the internal `/api/auth/refresh` sub-call.\n\t */\n\ttimeoutMs?: number;\n}\n\n/** Default per-attempt timeout when `timeoutMs` is not provided. */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/**\n * Build the effective per-attempt AbortSignal for a single fetch attempt.\n *\n * - When `timeoutMs` disables the timeout, we pass through the caller signal\n * (or `undefined`).\n * - When timeout is enabled and the caller supplied a signal, we compose them\n * with `AbortSignal.any` so whichever aborts first wins.\n *\n * Node engine is >=20 so `AbortSignal.any` / `AbortSignal.timeout` are\n * available; modern browsers (Chrome 116+, Firefox 124+, Safari 17.4+)\n * also ship them.\n */\nfunction buildAttemptSignal(\n\tcallerSignal: AbortSignal | null | undefined,\n\ttimeoutMs: number\n): AbortSignal | undefined {\n\tconst timeoutDisabled = timeoutMs === 0 || !Number.isFinite(timeoutMs);\n\tif (timeoutDisabled) {\n\t\treturn callerSignal ?? undefined;\n\t}\n\tconst timeoutSignal = AbortSignal.timeout(timeoutMs);\n\tif (!callerSignal) {\n\t\treturn timeoutSignal;\n\t}\n\treturn composeSignals([callerSignal, timeoutSignal]);\n}\n\n/**\n * Authenticated fetch wrapper with automatic token refresh.\n *\n * Applies a per-attempt timeout (default 60 s) to attempt #1 and to the\n * post-401-refresh retry. Each attempt gets its OWN fresh timeout budget so\n * a slow first attempt cannot starve the retry. See {@link AuthFetchOptions}.\n */\nexport async function authFetch(\n\turl: string | URL,\n\toptions?: AuthFetchOptions\n): Promise<Response> {\n\tconst { timeoutMs, signal: callerSignal, ...restOptions } = options ?? {};\n\tconst effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n\t// Attempt #1 — fresh timeout budget composed with caller signal.\n\tlet response = await fetch(url, {\n\t\t...restOptions,\n\t\tcredentials: 'include',\n\t\tsignal: buildAttemptSignal(callerSignal, effectiveTimeout)\n\t});\n\n\tif (response.status === 401) {\n\t\tconst refreshed = await refreshAccessToken(callerSignal);\n\n\t\tif (refreshed) {\n\t\t\t// Attempt #2 — brand new timeout budget. Do NOT reuse attempt #1's\n\t\t\t// signal (that budget may already be nearly exhausted); build a\n\t\t\t// fresh one instead. The caller's signal is still honored via\n\t\t\t// AbortSignal.any so external cancellation continues to work.\n\t\t\tresponse = await fetch(url, {\n\t\t\t\t...restOptions,\n\t\t\t\tcredentials: 'include',\n\t\t\t\tsignal: buildAttemptSignal(callerSignal, effectiveTimeout)\n\t\t\t});\n\t\t}\n\t\t// If refresh failed, do NOT hard-redirect to /auth.\n\t\t// With Sentinel's rotating refresh tokens, concurrent requests can\n\t\t// race: one wins the refresh (R1→R2) while the others fail because\n\t\t// R1 is already invalidated. A hard redirect would log the user out\n\t\t// even though the browser already has fresh cookies from the winning\n\t\t// response. Instead, return the original 401 response and let the\n\t\t// caller handle it — polling callers will silently retry on the next\n\t\t// interval with the updated cookies, and SvelteKit's protected-route\n\t\t// redirect handles truly expired sessions.\n\t}\n\n\treturn response;\n}\n\n/**\n * Authenticated fetch wrapper with JSON response parsing\n */\nexport async function authFetchJson<T = any>(\n\turl: string | URL,\n\toptions?: AuthFetchOptions\n): Promise<T> {\n\tconst response = await authFetch(url, options);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`HTTP error! status: ${response.status}`);\n\t}\n\n\treturn response.json();\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import type { Handle } from '@sveltejs/kit';
2
2
  import type { SentinelHandleOptions, UserLocals } from '../core/types.js';
3
+ /** Test-only: reset the once-per-isolate skip-verify warning/refusal latches. */
4
+ export declare function __resetSkipVerifyWarningForTests(): void;
3
5
  /** Locals shape set by the Sentinel auth middleware. */
4
6
  export interface SentinelLocals {
5
7
  user: UserLocals | null;
@@ -9,8 +11,9 @@ export interface SentinelLocals {
9
11
  * Creates a SvelteKit handle function with Sentinel authentication built-in.
10
12
  *
11
13
  * Features:
12
- * - Local JWT decode on every request (no per-request API call)
13
- * - API-validated token refresh when expired or structurally invalid
14
+ * - JWKS signature verification of the access token on every request
15
+ * (Ed25519/EdDSA, cached public keys — no per-request API call)
16
+ * - API-validated token refresh when expired or failing verification
14
17
  * - Local expiration check (saves API calls)
15
18
  * - Protected route redirection
16
19
  * - Sets event.locals.user for all routes
@@ -1 +1 @@
1
- {"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../src/sveltekit/handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAgC,MAAM,kBAAkB,CAAC;AAMxG,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CA0DhF"}
1
+ {"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../src/sveltekit/handle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAgC,MAAM,kBAAkB,CAAC;AAyCxG,iFAAiF;AACjF,wBAAgB,gCAAgC,IAAI,IAAI,CAGvD;AAED,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CA0EhF"}
@@ -1,13 +1,46 @@
1
1
  import { SentinelClient } from '../core/client.js';
2
2
  import { isTokenExpired, decodeTokenLocally } from '../core/auth-utils.js';
3
+ import { createJwksVerifier } from '../core/jwks.js';
3
4
  import { createLogger } from '../core/logger.js';
4
5
  import { COOKIE_NAMES, COOKIE_OPTIONS } from './cookies.js';
6
+ /**
7
+ * DEVELOPMENT-ONLY escape hatch. When set to "1" (or "true") the handle skips
8
+ * JWKS signature verification and trusts the decoded token payload, which is
9
+ * the pre-1.8.0 behaviour and the exact hole the admin-gate audit flagged.
10
+ * Never set this in production: when NODE_ENV=production the flag is REFUSED
11
+ * (construction throws for the programmatic option; per-request the flag is
12
+ * ignored with a loud classified error) and verification always runs.
13
+ */
14
+ const SKIP_VERIFY_ENV = 'SENTINEL_SKIP_JWKS_VERIFY';
15
+ let skipVerifyWarningEmitted = false;
16
+ function warnSkipVerifyOnce(log) {
17
+ if (skipVerifyWarningEmitted)
18
+ return;
19
+ skipVerifyWarningEmitted = true;
20
+ log.warn(`${SKIP_VERIFY_ENV} is enabled — JWT signatures are NOT being verified. ` +
21
+ 'Access-token payloads are trusted unverified. Development use only.');
22
+ }
23
+ let skipVerifyRefusalEmitted = false;
24
+ function refuseSkipVerifyInProduction(log) {
25
+ if (skipVerifyRefusalEmitted)
26
+ return;
27
+ skipVerifyRefusalEmitted = true;
28
+ log.error(`${SKIP_VERIFY_ENV} refused in production (NODE_ENV=production) — ` +
29
+ 'refusing to disable JWKS signature verification. ' +
30
+ 'Unset the flag or run with NODE_ENV=development.');
31
+ }
32
+ /** Test-only: reset the once-per-isolate skip-verify warning/refusal latches. */
33
+ export function __resetSkipVerifyWarningForTests() {
34
+ skipVerifyWarningEmitted = false;
35
+ skipVerifyRefusalEmitted = false;
36
+ }
5
37
  /**
6
38
  * Creates a SvelteKit handle function with Sentinel authentication built-in.
7
39
  *
8
40
  * Features:
9
- * - Local JWT decode on every request (no per-request API call)
10
- * - API-validated token refresh when expired or structurally invalid
41
+ * - JWKS signature verification of the access token on every request
42
+ * (Ed25519/EdDSA, cached public keys — no per-request API call)
43
+ * - API-validated token refresh when expired or failing verification
11
44
  * - Local expiration check (saves API calls)
12
45
  * - Protected route redirection
13
46
  * - Sets event.locals.user for all routes
@@ -16,6 +49,13 @@ import { COOKIE_NAMES, COOKIE_OPTIONS } from './cookies.js';
16
49
  export function createSentinelHandle(options = {}) {
17
50
  const { protectedRoutes = [], loginRedirect = '/auth' } = options;
18
51
  const log = options.logger ?? createLogger('sentinel-handle');
52
+ // Fail fast at construction when the unsafe escape hatch is requested in production.
53
+ if (options.skipJwksVerify === true &&
54
+ typeof process !== 'undefined' &&
55
+ process.env?.NODE_ENV === 'production') {
56
+ throw new Error(`${SKIP_VERIFY_ENV}: skipJwksVerify option refused in production ` +
57
+ '(NODE_ENV=production) — JWKS signature verification cannot be disabled.');
58
+ }
19
59
  return async ({ event, resolve }) => {
20
60
  // Initialize locals with typed shape
21
61
  const locals = event.locals;
@@ -23,25 +63,29 @@ export function createSentinelHandle(options = {}) {
23
63
  locals.accessToken = null;
24
64
  // Get config from environment or options (with request URL fallback)
25
65
  const config = getConfig(options, event.platform, event.request.url);
66
+ const skipVerify = isJwksVerifyDisabled(options, event.platform, log);
67
+ if (skipVerify)
68
+ warnSkipVerifyOnce(log);
69
+ const verifier = skipVerify ? null : createVerifier(options, config, event, log);
26
70
  const accessToken = event.cookies.get(COOKIE_NAMES.ACCESS_TOKEN);
27
71
  const refreshToken = event.cookies.get(COOKIE_NAMES.REFRESH_TOKEN);
28
72
  if (accessToken) {
29
73
  if (!isTokenExpired(accessToken)) {
30
- const decoded = decodeTokenLocally(accessToken);
74
+ const decoded = await resolveVerifiedToken(accessToken, verifier, log);
31
75
  if (decoded) {
32
76
  locals.user = createUserLocals(decoded);
33
77
  locals.accessToken = accessToken;
34
78
  }
35
79
  else {
36
- await tryRefreshToken(event, config, refreshToken, log);
80
+ await tryRefreshToken(event, config, refreshToken, log, verifier);
37
81
  }
38
82
  }
39
83
  else if (refreshToken) {
40
- await tryRefreshToken(event, config, refreshToken, log);
84
+ await tryRefreshToken(event, config, refreshToken, log, verifier);
41
85
  }
42
86
  }
43
87
  else if (refreshToken) {
44
- await tryRefreshToken(event, config, refreshToken, log);
88
+ await tryRefreshToken(event, config, refreshToken, log, verifier);
45
89
  }
46
90
  // Protected routes check — redirect to login with return URL
47
91
  const isProtectedRoute = protectedRoutes.some(route => event.url.pathname.startsWith(route));
@@ -61,6 +105,73 @@ export function createSentinelHandle(options = {}) {
61
105
  return response;
62
106
  };
63
107
  }
108
+ /**
109
+ * Verify an access token's signature against the auth server's JWKS and
110
+ * return the decoded payload only when the signature checks out.
111
+ * With `verifier === null` (explicit dev-only opt-out) falls back to the
112
+ * legacy unverified local decode. Never throws.
113
+ */
114
+ async function resolveVerifiedToken(accessToken, verifier, log) {
115
+ if (!verifier) {
116
+ return decodeTokenLocally(accessToken);
117
+ }
118
+ const outcome = await verifier.verify(accessToken);
119
+ if (outcome.status === 'valid') {
120
+ return outcome.payload;
121
+ }
122
+ log.warn('Access token rejected by signature verification', {
123
+ status: outcome.status,
124
+ reason: outcome.reason
125
+ });
126
+ return null;
127
+ }
128
+ function isProductionEnvironment(platform) {
129
+ const platformEnv = platform?.env;
130
+ const fromPlatform = platformEnv?.NODE_ENV;
131
+ const fromProcess = typeof process !== 'undefined' ? process.env?.NODE_ENV : undefined;
132
+ return (fromPlatform ?? fromProcess) === 'production';
133
+ }
134
+ function isJwksVerifyDisabled(options, platform, log) {
135
+ if (options.skipJwksVerify === true) {
136
+ if (isProductionEnvironment(platform)) {
137
+ refuseSkipVerifyInProduction(log);
138
+ return false;
139
+ }
140
+ return true;
141
+ }
142
+ const platformEnv = platform?.env;
143
+ const fromPlatform = platformEnv?.[SKIP_VERIFY_ENV];
144
+ const fromProcess = typeof process !== 'undefined' ? process.env?.[SKIP_VERIFY_ENV] : undefined;
145
+ const value = fromPlatform ?? fromProcess;
146
+ const requested = value === '1' || value === 'true';
147
+ if (requested && isProductionEnvironment(platform)) {
148
+ refuseSkipVerifyInProduction(log);
149
+ return false;
150
+ }
151
+ return requested;
152
+ }
153
+ function createVerifier(options, config, event, log) {
154
+ const platformEnv = event.platform?.env;
155
+ const jwksUrl = options.jwksUrl || platformEnv?.SENTINEL_JWKS_URL || deriveJwksUrl(config.apiUrl);
156
+ return createJwksVerifier({
157
+ jwksUrl,
158
+ fetchFn: event.fetch,
159
+ cacheTtlMs: options.jwksCacheTtlMs,
160
+ logger: log
161
+ });
162
+ }
163
+ /**
164
+ * Derive the JWKS URL from the Sentinel API URL. getConfig normalises apiUrl
165
+ * to `<origin>/api`, so stripping the `/api` suffix yields the origin that
166
+ * serves `/.well-known/jwks.json`.
167
+ */
168
+ function deriveJwksUrl(apiUrl) {
169
+ const origin = apiUrl.endsWith('/api') ? apiUrl.slice(0, -'/api'.length) : apiUrl;
170
+ let trimmed = origin;
171
+ while (trimmed.endsWith('/'))
172
+ trimmed = trimmed.slice(0, -1);
173
+ return `${trimmed}/.well-known/jwks.json`;
174
+ }
64
175
  function getConfig(options, platform, requestUrl) {
65
176
  let apiUrl = options.apiUrl || platform?.env?.SENTINEL_API_URL || '';
66
177
  let baseUrl = options.baseUrl || platform?.env?.VITE_BASE_URL || '';
@@ -112,7 +223,7 @@ function createUserLocals(decoded) {
112
223
  roles: decoded.roles || []
113
224
  };
114
225
  }
115
- async function tryRefreshToken(event, config, refreshToken, log) {
226
+ async function tryRefreshToken(event, config, refreshToken, log, verifier) {
116
227
  if (!refreshToken)
117
228
  return;
118
229
  try {
@@ -125,9 +236,11 @@ async function tryRefreshToken(event, config, refreshToken, log) {
125
236
  if (result.refresh_token) {
126
237
  event.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);
127
238
  }
128
- // Local decode — no second API call. The token was just issued by
129
- // the Sentinel backend during refresh, so it's already valid.
130
- const decoded = decodeTokenLocally(result.access_token);
239
+ // Verify the freshly-issued token's signature before trusting its
240
+ // payload (same JWKS path as the cookie flow). The cookie is kept —
241
+ // it was just minted by the backend over TLS — but locals.user is
242
+ // only set when verification passes; the next request retries.
243
+ const decoded = await resolveVerifiedToken(result.access_token, verifier, log);
131
244
  if (decoded) {
132
245
  const locals = event.locals;
133
246
  locals.user = createUserLocals(decoded);
@@ -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 { createLogger, type SentinelLogger } from '../core/logger.js';\nimport { COOKIE_NAMES, COOKIE_OPTIONS } from './cookies.js';\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 * - Local JWT decode on every request (no per-request API call)\n * - API-validated token refresh when expired or structurally invalid\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\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 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 = decodeTokenLocally(accessToken);\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);\n\t\t\t\t}\n\t\t\t} else if (refreshToken) {\n\t\t\t\tawait tryRefreshToken(event, config, refreshToken, log);\n\t\t\t}\n\t\t} else if (refreshToken) {\n\t\t\tawait tryRefreshToken(event, config, refreshToken, log);\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\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): 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// Local decode — no second API call. The token was just issued by\n\t\t\t\t// the Sentinel backend during refresh, so it's already valid.\n\t\t\t\tconst decoded = decodeTokenLocally(result.access_token);\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,MAAM,cAAc,CAAC;AAK5D;;;;;;;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,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;KAC1B,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;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 } 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 +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;AAElE,OAAO,KAAK,EACX,kBAAkB,EAGlB,MAAM,qBAAqB,CAAC;AA+gB7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CA8ClH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,eAAe,CAAC;AAElE,OAAO,KAAK,EACX,kBAAkB,EAGlB,MAAM,qBAAqB,CAAC;AAgiB7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CA8ClH"}