@nexartis/sentinel-sdk 1.9.0 → 1.9.2

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 CHANGED
@@ -51,6 +51,19 @@ downstream authorization gates.
51
51
  refresh to pick up rotated keys.
52
52
  - Only `alg: EdDSA` is accepted. `HS256`, `none`, and any other algorithm are
53
53
  rejected outright.
54
+ - Since **1.9.2**, signed claims must match the backend's current exact
55
+ `iss: "nexartis"`, `aud: "nexartis"` tuple. Subject, email and JTI must be
56
+ nonempty strings; roles must be an array of strings. `iat`, `exp`, and
57
+ optional `nbf` must be finite nonnegative seconds (including fractional
58
+ seconds) with bounded millisecond conversion. Expired tokens and tokens before `nbf` are rejected.
59
+ - Valid verifier outcomes return only the qualified issuer, audience, subject,
60
+ email, roles, JTI and NumericDate claims. Unqualified additional claims do not
61
+ acquire tenant, client, membership, or human-presence semantics.
62
+ `DecodedToken` and `decodeTokenLocally` alone are **not** verification evidence.
63
+ - Local JWKS verification **does not query backend JTI revocation**, current
64
+ account status or membership, product/tenant authorization, or proof of humanity.
65
+ The current audience is a service-wide constant, not client-specific isolation.
66
+ Those checks require the appropriate backend/product authority boundary.
54
67
  - Verification **never throws**. If the JWKS document is unreachable the
55
68
  request fails closed to unauthenticated (not a 500).
56
69
 
@@ -38,11 +38,18 @@ export interface JwksVerifierOptions {
38
38
  /** Optional structured logger. */
39
39
  logger?: SentinelLogger;
40
40
  }
41
+ /** Signature-verified current Sentinel access claims, not revocation or membership evidence. */
42
+ export interface VerifiedAccessToken extends DecodedToken {
43
+ iss: 'nexartis';
44
+ aud: 'nexartis';
45
+ jti: string;
46
+ nbf?: number;
47
+ }
41
48
  export type TokenVerifyOutcome =
42
49
  /** Signature valid, payload structurally sound and not expired. */
43
50
  {
44
51
  status: 'valid';
45
- payload: DecodedToken;
52
+ payload: VerifiedAccessToken;
46
53
  }
47
54
  /**
48
55
  * Token rejected: bad signature, malformed token, unsupported alg,
@@ -1 +1 @@
1
- {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,mBAAmB;IACnC,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAClC,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,MAAM,kBAAkB;AAC7B,mEAAmE;AACjE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE;AAC5C;;;;GAIG;GACD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACvC;;;GAGG;GACD;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACnD,iEAAiE;IACjE,mBAAmB,IAAI,IAAI,CAAC;CAC5B;AA2BD,6CAA6C;AAC7C,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAoJD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,mBAAmB,GAAG,YAAY,CAgFjF"}
1
+ {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,mBAAmB;IACnC,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAClC,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,gGAAgG;AAChG,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACxD,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,kBAAkB;AAC7B,mEAAmE;AACjE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE;AACnD;;;;GAIG;GACD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACvC;;;GAGG;GACD;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACnD,iEAAiE;IACjE,mBAAmB,IAAI,IAAI,CAAC;CAC5B;AA4BD,6CAA6C;AAC7C,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAgKD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,mBAAmB,GAAG,YAAY,CAmFjF"}
package/dist/core/jwks.js CHANGED
@@ -29,6 +29,15 @@ function base64UrlDecodeToString(input) {
29
29
  return new TextDecoder('utf-8').decode(base64UrlDecodeToBytes(input));
30
30
  }
31
31
  async function importPublicEd25519Key(jwk) {
32
+ // Optional JWK metadata must not contradict the key's advertised verification purpose.
33
+ if ('alg' in jwk && jwk.alg !== 'EdDSA')
34
+ return null;
35
+ if ('use' in jwk && jwk.use !== 'sig')
36
+ return null;
37
+ if ('key_ops' in jwk && (!Array.isArray(jwk.key_ops) ||
38
+ !jwk.key_ops.every((operation) => typeof operation === 'string') ||
39
+ !jwk.key_ops.includes('verify')))
40
+ return null;
32
41
  if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519' || typeof jwk.x !== 'string' || jwk.x.length === 0) {
33
42
  return null;
34
43
  }
@@ -121,27 +130,35 @@ function parseTokenHeader(token) {
121
130
  return null;
122
131
  }
123
132
  }
133
+ function isSafeNumericDate(value) {
134
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 &&
135
+ value * 1000 <= Number.MAX_SAFE_INTEGER;
136
+ }
124
137
  function parseAndValidatePayload(payloadPart) {
125
138
  try {
126
139
  const payload = JSON.parse(base64UrlDecodeToString(payloadPart));
127
- if (typeof payload !== 'object' || payload === null)
140
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload))
141
+ return null;
142
+ // Current backend contract is a fixed service tuple, not a client/tenant audience.
143
+ if (payload.iss !== 'nexartis' || payload.aud !== 'nexartis')
128
144
  return null;
129
145
  if (typeof payload.sub !== 'string' || payload.sub.length === 0)
130
146
  return null;
131
147
  if (typeof payload.email !== 'string' || payload.email.length === 0)
132
148
  return null;
133
- if (typeof payload.exp !== 'number' || isNaN(payload.exp))
149
+ if (typeof payload.jti !== 'string' || payload.jti.length === 0)
150
+ return null;
151
+ if (!isSafeNumericDate(payload.exp) || !isSafeNumericDate(payload.iat))
134
152
  return null;
135
- if (typeof payload.iat !== 'number' || isNaN(payload.iat))
153
+ if ('nbf' in payload && !isSafeNumericDate(payload.nbf))
136
154
  return null;
155
+ if (!Array.isArray(payload.roles) || !payload.roles.every((role) => typeof role === 'string'))
156
+ return null;
157
+ // The signature covers these original claims. Do not normalize or silently drop roles.
137
158
  return {
138
- sub: payload.sub,
139
- email: payload.email,
140
- roles: Array.isArray(payload.roles)
141
- ? payload.roles.filter((r) => typeof r === 'string')
142
- : [],
143
- exp: payload.exp,
144
- iat: payload.iat
159
+ iss: payload.iss, aud: payload.aud, sub: payload.sub, email: payload.email,
160
+ jti: payload.jti, roles: payload.roles, iat: payload.iat, exp: payload.exp,
161
+ ...('nbf' in payload ? { nbf: payload.nbf } : {})
145
162
  };
146
163
  }
147
164
  catch {
@@ -213,6 +230,9 @@ export function createJwksVerifier(userOptions) {
213
230
  if (Date.now() >= payload.exp * 1000) {
214
231
  return { status: 'invalid', reason: 'token expired' };
215
232
  }
233
+ if (payload.nbf !== undefined && Date.now() < payload.nbf * 1000) {
234
+ return { status: 'invalid', reason: 'token not yet valid' };
235
+ }
216
236
  return { status: 'valid', payload };
217
237
  }
218
238
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"jwks.js","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"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","sourcesContent":["// src/core/jwks.ts\n\n/**\n * JWKS-based access-token signature verification (Ed25519 / EdDSA).\n *\n * Security model: the Sentinel backend signs access tokens with EdDSA and\n * publishes the public key at `/.well-known/jwks.json` (RFC 7517). This\n * module fetches and caches that key set, then verifies each access token's\n * signature with WebCrypto BEFORE the payload is trusted. Tokens that fail\n * verification are treated as unauthenticated — verification never throws.\n *\n * Cache behaviour:\n * - JWKS documents are cached per isolate, keyed by URL, with a TTL\n * (default 10 minutes).\n * - A token whose `kid` is not in the cache triggers ONE forced refresh\n * (key rotation support), throttled to at most once per\n * `refreshMinIntervalMs` (default 10 seconds) so unknown-kid floods cannot\n * be used to hammer the auth server.\n * - JWKS fetch failure classifies as `unavailable` (treat as\n * unauthenticated); it never surfaces as a 500.\n *\n * Only OKP/Ed25519 keys with `alg: EdDSA` semantics are accepted. Tokens\n * with any other algorithm (including legacy HS256) are rejected as\n * `invalid` — a symmetric HS256 secret can never be verified from a public\n * JWKS, and accepting it here would reintroduce the unverified-payload hole.\n */\n\nimport type { DecodedToken } from './types.js';\nimport type { SentinelLogger } from './logger.js';\nimport { createLogger } from './logger.js';\n\nexport interface JwksVerifierOptions {\n\t/** Full URL of the JWKS document, e.g. https://auth.example.com/.well-known/jwks.json */\n\tjwksUrl: string;\n\t/** Fetch implementation. Defaults to globalThis.fetch (pass event.fetch in SvelteKit handles). */\n\tfetchFn?: typeof globalThis.fetch;\n\t/** Cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tcacheTtlMs?: number;\n\t/** Minimum interval between forced refreshes for unknown kids, in ms. Default: 10_000. */\n\trefreshMinIntervalMs?: number;\n\t/** Timeout for each JWKS fetch, in ms. Default: 5_000. */\n\ttimeoutMs?: number;\n\t/** Optional structured logger. */\n\tlogger?: SentinelLogger;\n}\n\nexport type TokenVerifyOutcome =\n\t/** Signature valid, payload structurally sound and not expired. */\n\t| { status: 'valid'; payload: DecodedToken }\n\t/**\n\t * Token rejected: bad signature, malformed token, unsupported alg,\n\t * unknown kid (after refresh), expired, or structurally invalid payload.\n\t * Treat the request as unauthenticated.\n\t */\n\t| { status: 'invalid'; reason: string }\n\t/**\n\t * Verification could not run because the JWKS document is unreachable or\n\t * unusable. Treat the request as unauthenticated (fail closed) — never a 500.\n\t */\n\t| { status: 'unavailable'; reason: string };\n\nexport interface JwksVerifier {\n\tverify(token: string): Promise<TokenVerifyOutcome>;\n\t/** Test-only: drop cached JWKS state for this verifier's URL. */\n\t_resetCacheForTests(): void;\n}\n\ninterface JwksCacheEntry {\n\tkeys: Map<string, CryptoKey>;\n\tfetchedAt: number;\n}\n\ninterface JwksKeyShape {\n\tkty?: unknown;\n\tcrv?: unknown;\n\tx?: unknown;\n\tkid?: unknown;\n\talg?: unknown;\n\tuse?: unknown;\n}\n\ninterface JwksDocumentShape {\n\tkeys?: unknown;\n}\n\n// Per-isolate cache shared across verifiers with the same JWKS URL.\nconst jwksCache = new Map<string, JwksCacheEntry>();\n// Per-URL in-flight fetch dedup (avoids thundering herd on cold cache).\nconst inflightFetches = new Map<string, Promise<JwksCacheEntry | null>>();\n// Per-URL throttle markers for forced refreshes on unknown kid.\nconst lastForcedRefreshAt = new Map<string, number>();\n\n/** Test-only: drop all cached JWKS state. */\nexport function __resetJwksCacheForTests(): void {\n\tjwksCache.clear();\n\tinflightFetches.clear();\n\tlastForcedRefreshAt.clear();\n}\n\nfunction base64UrlDecodeToBytes(input: string): Uint8Array<ArrayBuffer> {\n\tlet base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n\tconst pad = base64.length % 4;\n\tif (pad) base64 += '='.repeat(4 - pad);\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(new ArrayBuffer(binary.length));\n\tfor (let i = 0; i < binary.length; i += 1) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\nfunction base64UrlDecodeToString(input: string): string {\n\t// Decode via Uint8Array + TextDecoder for proper UTF-8 handling.\n\treturn new TextDecoder('utf-8').decode(base64UrlDecodeToBytes(input));\n}\n\nasync function importPublicEd25519Key(jwk: JwksKeyShape): Promise<CryptoKey | null> {\n\tif (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519' || typeof jwk.x !== 'string' || jwk.x.length === 0) {\n\t\treturn null;\n\t}\n\ttry {\n\t\treturn await crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\t{ kty: 'OKP', crv: 'Ed25519', x: jwk.x },\n\t\t\t{ name: 'Ed25519' },\n\t\t\tfalse,\n\t\t\t['verify']\n\t\t);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchJwksDocument(\n\tjwksUrl: string,\n\tfetchFn: typeof globalThis.fetch,\n\ttimeoutMs: number,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\tconst response = await fetchFn(jwksUrl, {\n\t\t\tsignal: controller.signal,\n\t\t\theaders: { accept: 'application/json' }\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlog.warn('JWKS fetch returned non-OK status', { jwksUrl, status: response.status });\n\t\t\treturn null;\n\t\t}\n\t\tconst document = (await response.json()) as JwksDocumentShape;\n\t\tif (!Array.isArray(document.keys)) {\n\t\t\tlog.warn('JWKS document has no keys array', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\tconst keys = new Map<string, CryptoKey>();\n\t\tfor (const rawKey of document.keys as JwksKeyShape[]) {\n\t\t\tif (typeof rawKey?.kid !== 'string' || rawKey.kid.length === 0) continue;\n\t\t\tconst imported = await importPublicEd25519Key(rawKey);\n\t\t\tif (imported) keys.set(rawKey.kid, imported);\n\t\t}\n\t\tif (keys.size === 0) {\n\t\t\tlog.warn('JWKS document contained no usable Ed25519 keys', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\treturn { keys, fetchedAt: Date.now() };\n\t} catch (error) {\n\t\tlog.warn('JWKS fetch failed', { jwksUrl, error: String(error) });\n\t\treturn null;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\nasync function getJwksEntry(\n\toptions: Required<Pick<JwksVerifierOptions, 'jwksUrl' | 'cacheTtlMs' | 'refreshMinIntervalMs' | 'timeoutMs'>> & JwksVerifierOptions,\n\tforce: boolean,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst { jwksUrl, cacheTtlMs, refreshMinIntervalMs, timeoutMs } = options;\n\tconst fetchFn = options.fetchFn ?? globalThis.fetch;\n\tconst now = Date.now();\n\n\tconst cached = jwksCache.get(jwksUrl);\n\tif (!force && cached && now - cached.fetchedAt < cacheTtlMs) {\n\t\treturn cached;\n\t}\n\n\tif (force) {\n\t\tconst lastForced = lastForcedRefreshAt.get(jwksUrl) ?? 0;\n\t\tif (now - lastForced < refreshMinIntervalMs) {\n\t\t\t// Throttled: report whatever we have (possibly nothing).\n\t\t\treturn cached ?? null;\n\t\t}\n\t\tlastForcedRefreshAt.set(jwksUrl, now);\n\t}\n\n\tconst inflight = inflightFetches.get(jwksUrl);\n\tif (inflight) return inflight;\n\n\tconst pending = fetchJwksDocument(jwksUrl, fetchFn, timeoutMs, log)\n\t\t.then((entry) => {\n\t\t\tif (entry) jwksCache.set(jwksUrl, entry);\n\t\t\treturn entry;\n\t\t})\n\t\t.finally(() => {\n\t\t\tinflightFetches.delete(jwksUrl);\n\t\t});\n\tinflightFetches.set(jwksUrl, pending);\n\treturn pending;\n}\n\nfunction parseTokenHeader(token: string): { alg?: unknown; kid?: unknown } | null {\n\ttry {\n\t\tconst parts = token.split('.');\n\t\tif (parts.length !== 3) return null;\n\t\tconst header = JSON.parse(base64UrlDecodeToString(parts[0]));\n\t\treturn typeof header === 'object' && header !== null ? (header as { alg?: unknown; kid?: unknown }) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction parseAndValidatePayload(payloadPart: string): DecodedToken | null {\n\ttry {\n\t\tconst payload = JSON.parse(base64UrlDecodeToString(payloadPart));\n\t\tif (typeof payload !== 'object' || payload === null) return null;\n\t\tif (typeof payload.sub !== 'string' || payload.sub.length === 0) return null;\n\t\tif (typeof payload.email !== 'string' || payload.email.length === 0) return null;\n\t\tif (typeof payload.exp !== 'number' || isNaN(payload.exp)) return null;\n\t\tif (typeof payload.iat !== 'number' || isNaN(payload.iat)) return null;\n\t\treturn {\n\t\t\tsub: payload.sub,\n\t\t\temail: payload.email,\n\t\t\troles: Array.isArray(payload.roles)\n\t\t\t\t? payload.roles.filter((r: unknown): r is string => typeof r === 'string')\n\t\t\t\t: [],\n\t\t\texp: payload.exp,\n\t\t\tiat: payload.iat\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Create a JWKS verifier bound to one JWKS URL. The underlying key cache is\n * shared per isolate across verifiers with the same URL.\n */\nexport function createJwksVerifier(userOptions: JwksVerifierOptions): JwksVerifier {\n\tconst options = {\n\t\tjwksUrl: userOptions.jwksUrl,\n\t\tfetchFn: userOptions.fetchFn,\n\t\tcacheTtlMs: userOptions.cacheTtlMs ?? 600_000,\n\t\trefreshMinIntervalMs: userOptions.refreshMinIntervalMs ?? 10_000,\n\t\ttimeoutMs: userOptions.timeoutMs ?? 5_000,\n\t\tlogger: userOptions.logger\n\t};\n\tconst log = options.logger ?? createLogger('sentinel-jwks');\n\n\tasync function resolveKey(kid: string): Promise<{ key?: CryptoKey; entry: JwksCacheEntry | null }> {\n\t\tlet entry = await getJwksEntry(options, false, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\n\t\t// Unknown kid (or no cache): one throttled forced refresh for key rotation.\n\t\tentry = await getJwksEntry(options, true, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\t\treturn { entry };\n\t}\n\n\treturn {\n\t\tasync verify(token: string): Promise<TokenVerifyOutcome> {\n\t\t\ttry {\n\t\t\t\tconst parts = token.split('.');\n\t\t\t\tif (parts.length !== 3) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'malformed token structure' };\n\t\t\t\t}\n\t\t\t\tconst [headerPart, payloadPart, signaturePart] = parts;\n\n\t\t\t\tconst header = parseTokenHeader(token);\n\t\t\t\tif (!header) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'unparseable token header' };\n\t\t\t\t}\n\t\t\t\tif (header.alg !== 'EdDSA') {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'invalid',\n\t\t\t\t\t\treason: `unsupported token alg \"${String(header.alg)}\" — only EdDSA tokens can be JWKS-verified (backend must be migrated from HS256)`\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (typeof header.kid !== 'string' || header.kid.length === 0) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token header missing kid' };\n\t\t\t\t}\n\n\t\t\t\tconst { key, entry } = await resolveKey(header.kid);\n\t\t\t\tif (!entry) {\n\t\t\t\t\treturn { status: 'unavailable', reason: 'JWKS document unreachable' };\n\t\t\t\t}\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn { status: 'invalid', reason: `unknown kid \"${header.kid}\" (after JWKS refresh)` };\n\t\t\t\t}\n\n\t\t\t\tconst signature = base64UrlDecodeToBytes(signaturePart);\n\t\t\t\tconst data = new TextEncoder().encode(`${headerPart}.${payloadPart}`);\n\t\t\t\tconst signatureValid = await crypto.subtle.verify('Ed25519', key, signature, data);\n\t\t\t\tif (!signatureValid) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'signature verification failed' };\n\t\t\t\t}\n\n\t\t\t\tconst payload = parseAndValidatePayload(payloadPart);\n\t\t\t\tif (!payload) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'payload missing required claims' };\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= payload.exp * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token expired' };\n\t\t\t\t}\n\t\t\t\treturn { status: 'valid', payload };\n\t\t\t} catch (error) {\n\t\t\t\t// Never throw out of verification — classify and fail closed.\n\t\t\t\tlog.error('Unexpected JWKS verification error', { error: String(error) });\n\t\t\t\treturn { status: 'invalid', reason: 'verification error' };\n\t\t\t}\n\t\t},\n\n\t\t_resetCacheForTests(): void {\n\t\t\tjwksCache.delete(options.jwksUrl);\n\t\t\tlastForcedRefreshAt.delete(options.jwksUrl);\n\t\t\tinflightFetches.delete(options.jwksUrl);\n\t\t}\n\t};\n}\n"]}
1
+ {"version":3,"file":"jwks.js","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"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","sourcesContent":["// src/core/jwks.ts\n\n/**\n * JWKS-based access-token signature verification (Ed25519 / EdDSA).\n *\n * Security model: the Sentinel backend signs access tokens with EdDSA and\n * publishes the public key at `/.well-known/jwks.json` (RFC 7517). This\n * module fetches and caches that key set, then verifies each access token's\n * signature with WebCrypto BEFORE the payload is trusted. Tokens that fail\n * verification are treated as unauthenticated — verification never throws.\n *\n * Cache behaviour:\n * - JWKS documents are cached per isolate, keyed by URL, with a TTL\n * (default 10 minutes).\n * - A token whose `kid` is not in the cache triggers ONE forced refresh\n * (key rotation support), throttled to at most once per\n * `refreshMinIntervalMs` (default 10 seconds) so unknown-kid floods cannot\n * be used to hammer the auth server.\n * - JWKS fetch failure classifies as `unavailable` (treat as\n * unauthenticated); it never surfaces as a 500.\n *\n * Only OKP/Ed25519 keys with `alg: EdDSA` semantics are accepted. Tokens\n * with any other algorithm (including legacy HS256) are rejected as\n * `invalid` — a symmetric HS256 secret can never be verified from a public\n * JWKS, and accepting it here would reintroduce the unverified-payload hole.\n */\n\nimport type { DecodedToken } from './types.js';\nimport type { SentinelLogger } from './logger.js';\nimport { createLogger } from './logger.js';\n\nexport interface JwksVerifierOptions {\n\t/** Full URL of the JWKS document, e.g. https://auth.example.com/.well-known/jwks.json */\n\tjwksUrl: string;\n\t/** Fetch implementation. Defaults to globalThis.fetch (pass event.fetch in SvelteKit handles). */\n\tfetchFn?: typeof globalThis.fetch;\n\t/** Cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tcacheTtlMs?: number;\n\t/** Minimum interval between forced refreshes for unknown kids, in ms. Default: 10_000. */\n\trefreshMinIntervalMs?: number;\n\t/** Timeout for each JWKS fetch, in ms. Default: 5_000. */\n\ttimeoutMs?: number;\n\t/** Optional structured logger. */\n\tlogger?: SentinelLogger;\n}\n\n/** Signature-verified current Sentinel access claims, not revocation or membership evidence. */\nexport interface VerifiedAccessToken extends DecodedToken {\n\tiss: 'nexartis';\n\taud: 'nexartis';\n\tjti: string;\n\tnbf?: number;\n}\n\nexport type TokenVerifyOutcome =\n\t/** Signature valid, payload structurally sound and not expired. */\n\t| { status: 'valid'; payload: VerifiedAccessToken }\n\t/**\n\t * Token rejected: bad signature, malformed token, unsupported alg,\n\t * unknown kid (after refresh), expired, or structurally invalid payload.\n\t * Treat the request as unauthenticated.\n\t */\n\t| { status: 'invalid'; reason: string }\n\t/**\n\t * Verification could not run because the JWKS document is unreachable or\n\t * unusable. Treat the request as unauthenticated (fail closed) — never a 500.\n\t */\n\t| { status: 'unavailable'; reason: string };\n\nexport interface JwksVerifier {\n\tverify(token: string): Promise<TokenVerifyOutcome>;\n\t/** Test-only: drop cached JWKS state for this verifier's URL. */\n\t_resetCacheForTests(): void;\n}\n\ninterface JwksCacheEntry {\n\tkeys: Map<string, CryptoKey>;\n\tfetchedAt: number;\n}\n\ninterface JwksKeyShape {\n\tkty?: unknown;\n\tcrv?: unknown;\n\tx?: unknown;\n\tkid?: unknown;\n\talg?: unknown;\n\tuse?: unknown;\n\tkey_ops?: unknown;\n}\n\ninterface JwksDocumentShape {\n\tkeys?: unknown;\n}\n\n// Per-isolate cache shared across verifiers with the same JWKS URL.\nconst jwksCache = new Map<string, JwksCacheEntry>();\n// Per-URL in-flight fetch dedup (avoids thundering herd on cold cache).\nconst inflightFetches = new Map<string, Promise<JwksCacheEntry | null>>();\n// Per-URL throttle markers for forced refreshes on unknown kid.\nconst lastForcedRefreshAt = new Map<string, number>();\n\n/** Test-only: drop all cached JWKS state. */\nexport function __resetJwksCacheForTests(): void {\n\tjwksCache.clear();\n\tinflightFetches.clear();\n\tlastForcedRefreshAt.clear();\n}\n\nfunction base64UrlDecodeToBytes(input: string): Uint8Array<ArrayBuffer> {\n\tlet base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n\tconst pad = base64.length % 4;\n\tif (pad) base64 += '='.repeat(4 - pad);\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(new ArrayBuffer(binary.length));\n\tfor (let i = 0; i < binary.length; i += 1) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\nfunction base64UrlDecodeToString(input: string): string {\n\t// Decode via Uint8Array + TextDecoder for proper UTF-8 handling.\n\treturn new TextDecoder('utf-8').decode(base64UrlDecodeToBytes(input));\n}\n\nasync function importPublicEd25519Key(jwk: JwksKeyShape): Promise<CryptoKey | null> {\n\t// Optional JWK metadata must not contradict the key's advertised verification purpose.\n\tif ('alg' in jwk && jwk.alg !== 'EdDSA') return null;\n\tif ('use' in jwk && jwk.use !== 'sig') return null;\n\tif ('key_ops' in jwk && (!Array.isArray(jwk.key_ops) ||\n\t\t!jwk.key_ops.every((operation: unknown) => typeof operation === 'string') ||\n\t\t!jwk.key_ops.includes('verify'))) return null;\n\tif (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519' || typeof jwk.x !== 'string' || jwk.x.length === 0) {\n\t\treturn null;\n\t}\n\ttry {\n\t\treturn await crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\t{ kty: 'OKP', crv: 'Ed25519', x: jwk.x },\n\t\t\t{ name: 'Ed25519' },\n\t\t\tfalse,\n\t\t\t['verify']\n\t\t);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchJwksDocument(\n\tjwksUrl: string,\n\tfetchFn: typeof globalThis.fetch,\n\ttimeoutMs: number,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\tconst response = await fetchFn(jwksUrl, {\n\t\t\tsignal: controller.signal,\n\t\t\theaders: { accept: 'application/json' }\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlog.warn('JWKS fetch returned non-OK status', { jwksUrl, status: response.status });\n\t\t\treturn null;\n\t\t}\n\t\tconst document = (await response.json()) as JwksDocumentShape;\n\t\tif (!Array.isArray(document.keys)) {\n\t\t\tlog.warn('JWKS document has no keys array', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\tconst keys = new Map<string, CryptoKey>();\n\t\tfor (const rawKey of document.keys as JwksKeyShape[]) {\n\t\t\tif (typeof rawKey?.kid !== 'string' || rawKey.kid.length === 0) continue;\n\t\t\tconst imported = await importPublicEd25519Key(rawKey);\n\t\t\tif (imported) keys.set(rawKey.kid, imported);\n\t\t}\n\t\tif (keys.size === 0) {\n\t\t\tlog.warn('JWKS document contained no usable Ed25519 keys', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\treturn { keys, fetchedAt: Date.now() };\n\t} catch (error) {\n\t\tlog.warn('JWKS fetch failed', { jwksUrl, error: String(error) });\n\t\treturn null;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\nasync function getJwksEntry(\n\toptions: Required<Pick<JwksVerifierOptions, 'jwksUrl' | 'cacheTtlMs' | 'refreshMinIntervalMs' | 'timeoutMs'>> & JwksVerifierOptions,\n\tforce: boolean,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst { jwksUrl, cacheTtlMs, refreshMinIntervalMs, timeoutMs } = options;\n\tconst fetchFn = options.fetchFn ?? globalThis.fetch;\n\tconst now = Date.now();\n\n\tconst cached = jwksCache.get(jwksUrl);\n\tif (!force && cached && now - cached.fetchedAt < cacheTtlMs) {\n\t\treturn cached;\n\t}\n\n\tif (force) {\n\t\tconst lastForced = lastForcedRefreshAt.get(jwksUrl) ?? 0;\n\t\tif (now - lastForced < refreshMinIntervalMs) {\n\t\t\t// Throttled: report whatever we have (possibly nothing).\n\t\t\treturn cached ?? null;\n\t\t}\n\t\tlastForcedRefreshAt.set(jwksUrl, now);\n\t}\n\n\tconst inflight = inflightFetches.get(jwksUrl);\n\tif (inflight) return inflight;\n\n\tconst pending = fetchJwksDocument(jwksUrl, fetchFn, timeoutMs, log)\n\t\t.then((entry) => {\n\t\t\tif (entry) jwksCache.set(jwksUrl, entry);\n\t\t\treturn entry;\n\t\t})\n\t\t.finally(() => {\n\t\t\tinflightFetches.delete(jwksUrl);\n\t\t});\n\tinflightFetches.set(jwksUrl, pending);\n\treturn pending;\n}\n\nfunction parseTokenHeader(token: string): { alg?: unknown; kid?: unknown } | null {\n\ttry {\n\t\tconst parts = token.split('.');\n\t\tif (parts.length !== 3) return null;\n\t\tconst header = JSON.parse(base64UrlDecodeToString(parts[0]));\n\t\treturn typeof header === 'object' && header !== null ? (header as { alg?: unknown; kid?: unknown }) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isSafeNumericDate(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value) && value >= 0 &&\n\t\tvalue * 1000 <= Number.MAX_SAFE_INTEGER;\n}\n\nfunction parseAndValidatePayload(payloadPart: string): VerifiedAccessToken | null {\n\ttry {\n\t\tconst payload = JSON.parse(base64UrlDecodeToString(payloadPart));\n\t\tif (typeof payload !== 'object' || payload === null || Array.isArray(payload)) return null;\n\t\t// Current backend contract is a fixed service tuple, not a client/tenant audience.\n\t\tif (payload.iss !== 'nexartis' || payload.aud !== 'nexartis') return null;\n\t\tif (typeof payload.sub !== 'string' || payload.sub.length === 0) return null;\n\t\tif (typeof payload.email !== 'string' || payload.email.length === 0) return null;\n\t\tif (typeof payload.jti !== 'string' || payload.jti.length === 0) return null;\n\t\tif (!isSafeNumericDate(payload.exp) || !isSafeNumericDate(payload.iat)) return null;\n\t\tif ('nbf' in payload && !isSafeNumericDate(payload.nbf)) return null;\n\t\tif (!Array.isArray(payload.roles) || !payload.roles.every((role: unknown) => typeof role === 'string')) return null;\n\t\t// The signature covers these original claims. Do not normalize or silently drop roles.\n\t\treturn {\n\t\t\tiss: payload.iss, aud: payload.aud, sub: payload.sub, email: payload.email,\n\t\t\tjti: payload.jti, roles: payload.roles, iat: payload.iat, exp: payload.exp,\n\t\t\t...('nbf' in payload ? { nbf: payload.nbf } : {})\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Create a JWKS verifier bound to one JWKS URL. The underlying key cache is\n * shared per isolate across verifiers with the same URL.\n */\nexport function createJwksVerifier(userOptions: JwksVerifierOptions): JwksVerifier {\n\tconst options = {\n\t\tjwksUrl: userOptions.jwksUrl,\n\t\tfetchFn: userOptions.fetchFn,\n\t\tcacheTtlMs: userOptions.cacheTtlMs ?? 600_000,\n\t\trefreshMinIntervalMs: userOptions.refreshMinIntervalMs ?? 10_000,\n\t\ttimeoutMs: userOptions.timeoutMs ?? 5_000,\n\t\tlogger: userOptions.logger\n\t};\n\tconst log = options.logger ?? createLogger('sentinel-jwks');\n\n\tasync function resolveKey(kid: string): Promise<{ key?: CryptoKey; entry: JwksCacheEntry | null }> {\n\t\tlet entry = await getJwksEntry(options, false, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\n\t\t// Unknown kid (or no cache): one throttled forced refresh for key rotation.\n\t\tentry = await getJwksEntry(options, true, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\t\treturn { entry };\n\t}\n\n\treturn {\n\t\tasync verify(token: string): Promise<TokenVerifyOutcome> {\n\t\t\ttry {\n\t\t\t\tconst parts = token.split('.');\n\t\t\t\tif (parts.length !== 3) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'malformed token structure' };\n\t\t\t\t}\n\t\t\t\tconst [headerPart, payloadPart, signaturePart] = parts;\n\n\t\t\t\tconst header = parseTokenHeader(token);\n\t\t\t\tif (!header) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'unparseable token header' };\n\t\t\t\t}\n\t\t\t\tif (header.alg !== 'EdDSA') {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'invalid',\n\t\t\t\t\t\treason: `unsupported token alg \"${String(header.alg)}\" — only EdDSA tokens can be JWKS-verified (backend must be migrated from HS256)`\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (typeof header.kid !== 'string' || header.kid.length === 0) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token header missing kid' };\n\t\t\t\t}\n\n\t\t\t\tconst { key, entry } = await resolveKey(header.kid);\n\t\t\t\tif (!entry) {\n\t\t\t\t\treturn { status: 'unavailable', reason: 'JWKS document unreachable' };\n\t\t\t\t}\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn { status: 'invalid', reason: `unknown kid \"${header.kid}\" (after JWKS refresh)` };\n\t\t\t\t}\n\n\t\t\t\tconst signature = base64UrlDecodeToBytes(signaturePart);\n\t\t\t\tconst data = new TextEncoder().encode(`${headerPart}.${payloadPart}`);\n\t\t\t\tconst signatureValid = await crypto.subtle.verify('Ed25519', key, signature, data);\n\t\t\t\tif (!signatureValid) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'signature verification failed' };\n\t\t\t\t}\n\n\t\t\t\tconst payload = parseAndValidatePayload(payloadPart);\n\t\t\t\tif (!payload) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'payload missing required claims' };\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= payload.exp * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token expired' };\n\t\t\t\t}\n\t\t\t\tif (payload.nbf !== undefined && Date.now() < payload.nbf * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token not yet valid' };\n\t\t\t\t}\n\t\t\t\treturn { status: 'valid', payload };\n\t\t\t} catch (error) {\n\t\t\t\t// Never throw out of verification — classify and fail closed.\n\t\t\t\tlog.error('Unexpected JWKS verification error', { error: String(error) });\n\t\t\t\treturn { status: 'invalid', reason: 'verification error' };\n\t\t\t}\n\t\t},\n\n\t\t_resetCacheForTests(): void {\n\t\t\tjwksCache.delete(options.jwksUrl);\n\t\t\tlastForcedRefreshAt.delete(options.jwksUrl);\n\t\t\tinflightFetches.delete(options.jwksUrl);\n\t\t}\n\t};\n}\n"]}
@@ -115,6 +115,7 @@ export interface SentinelResponse {
115
115
  error?: string;
116
116
  [key: string]: unknown;
117
117
  }
118
+ /** Decoded/API payload shape only: this type does not prove signature or claim verification. */
118
119
  export interface DecodedToken {
119
120
  sub: string;
120
121
  email: string;
@@ -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\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: 600 (10 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\n/** Decoded/API payload shape only: this type does not prove signature or claim verification. */\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: 600 (10 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/handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,eAAe,CAAC;AAGlE,OAAO,KAAK,EACX,kBAAkB,EAGlB,MAAM,qBAAqB,CAAC;AAqjB7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CA8ClH"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/handlers/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,eAAe,CAAC;AAGlE,OAAO,KAAK,EACX,kBAAkB,EAGlB,MAAM,qBAAqB,CAAC;AA0lB7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG;IAAE,GAAG,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CA8ClH"}
@@ -180,6 +180,20 @@ async function handleStartLogin(event, resolved) {
180
180
  });
181
181
  }
182
182
  }
183
+ function extractClientErrorMessage(error, defaultMsg) {
184
+ const body = error.body;
185
+ if (typeof body === 'string' && body.trim().length > 0) {
186
+ return body;
187
+ }
188
+ if (typeof body === 'object' && body !== null) {
189
+ const rec = body;
190
+ if (typeof rec.message === 'string' && rec.message.length > 0)
191
+ return rec.message;
192
+ if (typeof rec.error === 'string' && rec.error.length > 0)
193
+ return rec.error;
194
+ }
195
+ return defaultMsg;
196
+ }
183
197
  // Poll Handler
184
198
  async function handlePoll(event, resolved) {
185
199
  const { loginCookieOptions, log, opts } = resolved;
@@ -221,6 +235,9 @@ async function handlePoll(event, resolved) {
221
235
  log.error('Poll status error', { error: String(error) });
222
236
  if (opts.onError)
223
237
  await opts.onError(event, { handler: 'poll', error });
238
+ if (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {
239
+ return json({ status: 'error', error: extractClientErrorMessage(error, 'Polling failed') }, { status: error.status });
240
+ }
224
241
  return json({ status: 'error' }, { status: 500 });
225
242
  }
226
243
  }
@@ -323,6 +340,9 @@ async function handleClaimPost(event, resolved) {
323
340
  log.error('Claim tokens error', { error: String(error) });
324
341
  if (opts.onError)
325
342
  await opts.onError(event, { handler: 'claim', error });
343
+ if (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {
344
+ return json({ success: false, error: extractClientErrorMessage(error, 'Failed to claim tokens') }, { status: error.status });
345
+ }
326
346
  return json({ success: false, error: 'Internal server error' }, { status: 500 });
327
347
  }
328
348
  }
@@ -427,6 +447,9 @@ async function handleRefresh(event, resolved) {
427
447
  log.error('Refresh token error', { error: String(error) });
428
448
  if (opts.onError)
429
449
  await opts.onError(event, { handler: 'refresh', error });
450
+ if (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {
451
+ return json({ success: false, error: extractClientErrorMessage(error, 'Token refresh failed') }, { status: error.status });
452
+ }
430
453
  return json({ success: false, error: 'Internal server error' }, { status: 500 });
431
454
  }
432
455
  }
@@ -500,6 +523,9 @@ async function handleVerify(event, resolved) {
500
523
  log.error('Verify email error', { error: String(error) });
501
524
  if (opts.onError)
502
525
  await opts.onError(event, { handler: 'verify', error });
526
+ if (error instanceof SentinelApiError && error.status >= 400 && error.status < 500) {
527
+ return json({ success: false, error: extractClientErrorMessage(error, 'Verification failed') }, { status: error.status });
528
+ }
503
529
  return json({ success: false, error: 'Internal server error' }, { status: 500 });
504
530
  }
505
531
  }
@@ -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\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\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\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\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\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 { 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexartis/sentinel-sdk",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "Passwordless magic-link authentication SDK for SvelteKit — client for the hosted Nexartis Sentinel auth backend.",
5
5
  "repository": {
6
6
  "type": "git",