@nexartis/sentinel-sdk 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +43 -6
  2. package/dist/core/auth-utils.d.ts.map +1 -1
  3. package/dist/core/auth-utils.js +23 -3
  4. package/dist/core/auth-utils.js.map +1 -1
  5. package/dist/core/client.d.ts +4 -0
  6. package/dist/core/client.d.ts.map +1 -1
  7. package/dist/core/client.js +6 -0
  8. package/dist/core/client.js.map +1 -1
  9. package/dist/core/consumer-version-floor.d.ts +56 -0
  10. package/dist/core/consumer-version-floor.d.ts.map +1 -0
  11. package/dist/core/consumer-version-floor.js +233 -0
  12. package/dist/core/consumer-version-floor.js.map +1 -0
  13. package/dist/core/index.d.ts +2 -0
  14. package/dist/core/index.d.ts.map +1 -1
  15. package/dist/core/index.js +1 -0
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/core/jwks.d.ts +15 -1
  18. package/dist/core/jwks.d.ts.map +1 -1
  19. package/dist/core/jwks.js +54 -10
  20. package/dist/core/jwks.js.map +1 -1
  21. package/dist/core/types.d.ts +35 -1
  22. package/dist/core/types.d.ts.map +1 -1
  23. package/dist/core/types.js.map +1 -1
  24. package/dist/sveltekit/cookies.d.ts +10 -0
  25. package/dist/sveltekit/cookies.d.ts.map +1 -1
  26. package/dist/sveltekit/cookies.js +13 -0
  27. package/dist/sveltekit/cookies.js.map +1 -1
  28. package/dist/sveltekit/handle.js +8 -2
  29. package/dist/sveltekit/handle.js.map +1 -1
  30. package/dist/sveltekit/handlers/index.d.ts.map +1 -1
  31. package/dist/sveltekit/handlers/index.js +30 -5
  32. package/dist/sveltekit/handlers/index.js.map +1 -1
  33. package/dist/sveltekit/pages/index.d.ts.map +1 -1
  34. package/dist/sveltekit/pages/index.js +21 -10
  35. package/dist/sveltekit/pages/index.js.map +1 -1
  36. package/package.json +8 -3
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/pages/index.ts\nimport { redirect } from '@sveltejs/kit';\nimport { SentinelClient } from '../../core/client.js';\nimport { setSessionCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';\nimport { getConfigFromPlatform, clearLoginCookies } from '../utils.js';\nimport { validateToken } from '../../core/auth-utils.js';\n\n/** Sanitise a redirect path to prevent open-redirect via env misconfiguration. */\nfunction safeRedirectPath(raw: string | undefined): string {\n\tconst path = (raw || '/').trim();\n\tif (/^[/\\\\]{2}/.test(path) || /^[a-z][a-z0-9+.-]*:/i.test(path)) return '/';\n\treturn path.startsWith('/') ? path : '/';\n}\n\n/**\n * Detects SvelteKit redirects using ALL THREE methods.\n * CRITICAL: SvelteKit's Redirect object is NOT an instance of Response.\n */\nfunction isRedirectError(error: unknown): boolean {\n\treturn (\n\t\terror instanceof Response ||\n\t\t(error !== null && typeof error === 'object' && (error as any).constructor?.name === 'Redirect') ||\n\t\t(error !== null && typeof error === 'object' && 'status' in error && 'location' in error)\n\t);\n}\n\nexport interface MagicLinkResult {\n\tsuccess: boolean;\n\tcrossBrowser?: boolean;\n}\n\n/**\n * Handles the magic link page load.\n * TWO-STEP process: completePasswordless() → claimTokens()\n * \n * Usage in +page.server.ts:\n * ```typescript\n * import { handleMagicLink } from '@nexartis/sentinel-sdk/sveltekit/pages';\n * \n * export const load: PageServerLoad = async (event) => {\n * return handleMagicLink(event);\n * };\n * ```\n */\nexport async function handleMagicLink(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst magicToken = event.url.searchParams.get('token');\n\n\tif (!magicToken) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\t// Step 1: Complete the passwordless login (marks token as used)\n\t\tconst result = await sentinel.completePasswordless(magicToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\t// completePasswordless() only returns { code, message }\n\t\t\t// It does NOT return tokens - those must be claimed separately\n\n\t\t\tconst transactionId = event.cookies.get('login_transaction_id');\n\t\t\tconst clientToken = event.cookies.get('login_client_token');\n\t\t\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\t\t\t// Check if this is the same browser that initiated the login\n\t\t\tif (!transactionId || !clientToken) {\n\t\t\t\t// Different browser - show success message\n\t\t\t\tthrow redirect(302, '/auth?success=magic_link_clicked');\n\t\t\t}\n\n\t\t\t// Same browser - claim tokens immediately\n\t\t\t// Step 2: Claim the tokens\n\t\t\tconst claimResult = await sentinel.claimTokens(transactionId, clientToken);\n\n\t\t\tif (claimResult.code === 0 || claimResult.success) {\n\t\t\t\tif (claimResult.access_token && claimResult.refresh_token) {\n\t\t\t\t\tconst postAuthRedirect = safeRedirectPath(event.platform?.env?.SENTINEL_POST_AUTH_REDIRECT);\n\t\t\t\t\tsetSessionCookies(event.cookies, claimResult.access_token, claimResult.refresh_token, rememberMe);\n\t\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\t\tthrow redirect(303, postAuthRedirect);\n\t\t\t\t} else {\n\t\t\t\t\tthrow redirect(302, '/auth?error=no_tokens');\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow redirect(302, '/auth?error=claim_failed');\n\t\t\t}\n\t\t} else {\n\t\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\t// CRITICAL: Detect redirects using ALL three methods\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error; // Re-throw immediately\n\t\t}\n\n\t\tconsole.error('Magic link error:', error);\n\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t}\n}\n\n/**\n * Handles the email verification page load.\n * \n * **Breaking change (v1.1):** Now requires `cookies` on the event object\n * (previously only needed `url`, `fetch`, `platform`).\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerifyEmail } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerifyEmail(event);\n * };\n * ```\n */\nexport async function handleVerifyEmail(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst token = event.url.searchParams.get('token');\n\n\tif (!token) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\tconst result = await sentinel.verifyEmail(token);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token && result.refresh_token) {\n\t\t\t\t// SEC-001 fix: Set tokens in short-lived httpOnly cookies (NOT URL params).\n\t\t\t\t// These are consumed immediately by handleVerified on /auth/verified.\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tthrow redirect(303, '/auth/verified');\n\t\t\t} else {\n\t\t\t\t// Success but no tokens — clear any stale verify cookies from prior attempts\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tthrow redirect(302, '/auth?verified=true');\n\t\t\t}\n\t\t} else {\n\t\t\t// Clear any stale verify cookies from prior attempts\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error;\n\t\t}\n\n\t\t// Clear any stale verify cookies on error\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tconsole.error('Email verification error:', error);\n\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t}\n}\n\n/**\n * Handles the verified intermediate page load.\n * Reads tokens from short-lived httpOnly cookies (set by handleVerifyEmail),\n * validates the access token, then establishes the session.\n *\n * **Breaking change (v1.1):** Now requires `fetch` and `platform` on the event\n * object (previously only needed `url` and `cookies`) for server-side token validation.\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerified } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerified(event);\n * };\n * ```\n */\nexport async function handleVerified(event: {\n\turl: URL;\n\tcookies: any;\n\tfetch: typeof globalThis.fetch;\n\tplatform?: any;\n}): Promise<never> {\n\tconst accessToken = event.cookies.get(COOKIE_NAMES.VERIFY_ACCESS_TOKEN);\n\tconst refreshToken = event.cookies.get(COOKIE_NAMES.VERIFY_REFRESH_TOKEN);\n\t// Sanitize redirect target to prevent open-redirect attacks.\n\t// Only allow relative paths starting with a single '/'. Reject protocol-relative\n\t// URLs ('//evil.com'), absolute URLs ('https://evil.com'), backslash variants\n\t// ('/\\evil.com' which some user agents normalise to '//evil.com'), percent-encoded\n\t// backslashes ('%5c' / '%5C' which browsers may decode and normalise the same way),\n\t// and bare strings.\n\tconst rawRedirect = event.url.searchParams.get('redirect') || '/';\n\tconst redirectTo = (rawRedirect.startsWith('/') && !rawRedirect.startsWith('//') && !rawRedirect.includes('\\\\') && !rawRedirect.toLowerCase().includes('%5c')) ? rawRedirect : '/';\n\n\tif (!accessToken || !refreshToken) {\n\t\t// Clear any partial verify cookies to avoid stale state within the TTL\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// SEC-002 fix: Validate the token before consuming the cookies.\n\t// On transient network/API failures, leave cookies intact so the user can\n\t// retry within the 60s TTL without re-running the entire verification flow.\n\tconst config = getConfigFromPlatform(event.platform);\n\tlet decoded;\n\ttry {\n\t\tdecoded = await validateToken(accessToken, event.fetch, config);\n\t} catch {\n\t\t// Transient error — preserve cookies for retry\n\t\tthrow redirect(302, '/auth?error=verification_error');\n\t}\n\tif (!decoded) {\n\t\t// Definitively invalid token — clear cookies, no point retrying\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// Consume single-use verification cookies after successful validation\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\n\t// rememberMe=true is intentional: email verification proves device trust.\n\t// The original registration form's rememberMe preference cannot be reliably\n\t// carried through the async email verification flow. (ARCH-004)\n\tsetSessionCookies(event.cookies, accessToken, refreshToken, true);\n\n\t// Redirect to destination\n\tthrow redirect(303, redirectTo);\n}\n\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"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","sourcesContent":["// src/sveltekit/pages/index.ts\nimport { redirect } from '@sveltejs/kit';\nimport { SentinelClient } from '../../core/client.js';\nimport { setSessionCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';\nimport { getConfigFromPlatform, clearLoginCookies } from '../utils.js';\nimport { validateToken } from '../../core/auth-utils.js';\n\n/**\n * Sanitise a redirect path to prevent open-redirect via env misconfiguration or\n * a crafted `?redirect=` value. Only single-slash relative paths are allowed.\n * Rejects protocol-relative ('//x', '/\\x'), percent-encoded backslashes ('%5c'),\n * and any string carrying whitespace or ASCII control characters — browsers strip\n * tabs/newlines while parsing a URL, so `/\\t/evil.com` would otherwise normalise\n * to `//evil.com` and redirect cross-origin.\n */\nfunction safeRedirectPath(raw: string | undefined): string {\n\tconst path = (raw || '/').trim();\n\tif (!path.startsWith('/')) return '/';\n\tif (/^[/\\\\]{2}/.test(path)) return '/';\n\tif (/[\\s\\u0000-\\u001f\\u007f]/.test(path)) return '/';\n\tif (path.includes('\\\\') || path.toLowerCase().includes('%5c')) return '/';\n\treturn path;\n}\n\n/**\n * Detects SvelteKit redirects using ALL THREE methods.\n * CRITICAL: SvelteKit's Redirect object is NOT an instance of Response.\n */\nfunction isRedirectError(error: unknown): boolean {\n\treturn (\n\t\terror instanceof Response ||\n\t\t(error !== null && typeof error === 'object' && (error as any).constructor?.name === 'Redirect') ||\n\t\t(error !== null && typeof error === 'object' && 'status' in error && 'location' in error)\n\t);\n}\n\nexport interface MagicLinkResult {\n\tsuccess: boolean;\n\tcrossBrowser?: boolean;\n}\n\n/**\n * Handles the magic link page load.\n * TWO-STEP process: completePasswordless() → claimTokens()\n * \n * Usage in +page.server.ts:\n * ```typescript\n * import { handleMagicLink } from '@nexartis/sentinel-sdk/sveltekit/pages';\n * \n * export const load: PageServerLoad = async (event) => {\n * return handleMagicLink(event);\n * };\n * ```\n */\nexport async function handleMagicLink(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst magicToken = event.url.searchParams.get('token');\n\n\tif (!magicToken) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\t// Step 1: Complete the passwordless login (marks token as used)\n\t\tconst result = await sentinel.completePasswordless(magicToken);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\t// completePasswordless() only returns { code, message }\n\t\t\t// It does NOT return tokens - those must be claimed separately\n\n\t\t\tconst transactionId = event.cookies.get('login_transaction_id');\n\t\t\tconst clientToken = event.cookies.get('login_client_token');\n\t\t\tconst rememberMe = event.cookies.get('login_remember_me') === 'true';\n\n\t\t\t// Check if this is the same browser that initiated the login\n\t\t\tif (!transactionId || !clientToken) {\n\t\t\t\t// Different browser - show success message\n\t\t\t\tthrow redirect(302, '/auth?success=magic_link_clicked');\n\t\t\t}\n\n\t\t\t// Same browser - claim tokens immediately\n\t\t\t// Step 2: Claim the tokens\n\t\t\tconst claimResult = await sentinel.claimTokens(transactionId, clientToken);\n\n\t\t\tif (claimResult.code === 0 || claimResult.success) {\n\t\t\t\tif (claimResult.access_token && claimResult.refresh_token) {\n\t\t\t\t\tconst postAuthRedirect = safeRedirectPath(event.platform?.env?.SENTINEL_POST_AUTH_REDIRECT);\n\t\t\t\t\tsetSessionCookies(event.cookies, claimResult.access_token, claimResult.refresh_token, rememberMe);\n\t\t\t\t\tclearLoginCookies(event.cookies);\n\t\t\t\t\tthrow redirect(303, postAuthRedirect);\n\t\t\t\t} else {\n\t\t\t\t\tthrow redirect(302, '/auth?error=no_tokens');\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow redirect(302, '/auth?error=claim_failed');\n\t\t\t}\n\t\t} else {\n\t\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\t// CRITICAL: Detect redirects using ALL three methods\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error; // Re-throw immediately\n\t\t}\n\n\t\tconsole.error('Magic link error:', error);\n\t\tthrow redirect(302, '/auth?error=magic_link_failed');\n\t}\n}\n\n/**\n * Handles the email verification page load.\n * \n * **Breaking change (v1.1):** Now requires `cookies` on the event object\n * (previously only needed `url`, `fetch`, `platform`).\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerifyEmail } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerifyEmail(event);\n * };\n * ```\n */\nexport async function handleVerifyEmail(event: {\n\turl: URL;\n\tfetch: typeof globalThis.fetch;\n\tcookies: any;\n\tplatform?: any;\n}): Promise<never> {\n\tconst token = event.url.searchParams.get('token');\n\n\tif (!token) {\n\t\tthrow redirect(302, '/auth?error=invalid_link');\n\t}\n\n\tconst config = getConfigFromPlatform(event.platform);\n\tconst sentinel = new SentinelClient(config, event.fetch);\n\n\ttry {\n\t\tconst result = await sentinel.verifyEmail(token);\n\n\t\tif (result.code === 0 || result.success) {\n\t\t\tif (result.access_token && result.refresh_token) {\n\t\t\t\t// SEC-001 fix: Set tokens in short-lived httpOnly cookies (NOT URL params).\n\t\t\t\t// These are consumed immediately by handleVerified on /auth/verified.\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tevent.cookies.set(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.VERIFY_TOKEN);\n\t\t\t\tthrow redirect(303, '/auth/verified');\n\t\t\t} else {\n\t\t\t\t// Success but no tokens — clear any stale verify cookies from prior attempts\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\t\tthrow redirect(302, '/auth?verified=true');\n\t\t\t}\n\t\t} else {\n\t\t\t// Clear any stale verify cookies from prior attempts\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t\t}\n\t} catch (error: unknown) {\n\t\tif (isRedirectError(error)) {\n\t\t\tthrow error;\n\t\t}\n\n\t\t// Clear any stale verify cookies on error\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tconsole.error('Email verification error:', error);\n\t\tthrow redirect(302, '/auth?error=verification_failed');\n\t}\n}\n\n/**\n * Handles the verified intermediate page load.\n * Reads tokens from short-lived httpOnly cookies (set by handleVerifyEmail),\n * validates the access token, then establishes the session.\n *\n * **Breaking change (v1.1):** Now requires `fetch` and `platform` on the event\n * object (previously only needed `url` and `cookies`) for server-side token validation.\n *\n * Usage in +page.server.ts:\n * ```typescript\n * import { handleVerified } from '@nexartis/sentinel-sdk/sveltekit/pages';\n *\n * export const load: PageServerLoad = async (event) => {\n * return handleVerified(event);\n * };\n * ```\n */\nexport async function handleVerified(event: {\n\turl: URL;\n\tcookies: any;\n\tfetch: typeof globalThis.fetch;\n\tplatform?: any;\n}): Promise<never> {\n\tconst accessToken = event.cookies.get(COOKIE_NAMES.VERIFY_ACCESS_TOKEN);\n\tconst refreshToken = event.cookies.get(COOKIE_NAMES.VERIFY_REFRESH_TOKEN);\n\t// Sanitize redirect target to prevent open-redirect attacks. Only relative\n\t// paths starting with a single '/' survive; protocol-relative URLs, absolute\n\t// URLs, backslash variants, percent-encoded backslashes, and control/whitespace\n\t// characters (which browsers strip during URL parsing) all fall back to '/'.\n\tconst rawRedirect = event.url.searchParams.get('redirect') || '/';\n\tconst redirectTo = safeRedirectPath(rawRedirect);\n\n\tif (!accessToken || !refreshToken) {\n\t\t// Clear any partial verify cookies to avoid stale state within the TTL\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// SEC-002 fix: Validate the token before consuming the cookies.\n\t// On transient network/API failures, leave cookies intact so the user can\n\t// retry within the 60s TTL without re-running the entire verification flow.\n\tconst config = getConfigFromPlatform(event.platform);\n\tlet decoded;\n\ttry {\n\t\tdecoded = await validateToken(accessToken, event.fetch, config);\n\t} catch {\n\t\t// Transient error — preserve cookies for retry\n\t\tthrow redirect(302, '/auth?error=verification_error');\n\t}\n\tif (!decoded) {\n\t\t// Definitively invalid token — clear cookies, no point retrying\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\t\tthrow redirect(302, '/auth?error=invalid_verification');\n\t}\n\n\t// Consume single-use verification cookies after successful validation\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\tevent.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });\n\n\t// rememberMe=true is intentional: email verification proves device trust.\n\t// The original registration form's rememberMe preference cannot be reliably\n\t// carried through the async email verification flow. (ARCH-004)\n\tsetSessionCookies(event.cookies, accessToken, refreshToken, true);\n\n\t// Redirect to destination\n\tthrow redirect(303, redirectTo);\n}\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexartis/sentinel-sdk",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
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",
@@ -42,9 +42,11 @@
42
42
  },
43
43
  "files": [
44
44
  "dist",
45
+ "LICENSES",
45
46
  "NOTICE"
46
47
  ],
47
48
  "scripts": {
49
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
48
50
  "build": "tsc",
49
51
  "check": "tsc --noEmit",
50
52
  "dev": "tsc --watch",
@@ -52,8 +54,11 @@
52
54
  "test": "vitest run",
53
55
  "test:unit": "vitest run",
54
56
  "test:watch": "vitest",
55
- "validate": "pnpm run check && pnpm run test && pnpm run build",
56
- "prepublishOnly": "pnpm run build"
57
+ "conformance": "node scripts/conformance-sentinel-env.mjs",
58
+ "validate": "pnpm run check && pnpm run test && pnpm run conformance && pnpm run build && pnpm run verify:package && pnpm run gate:consumers",
59
+ "verify:package": "node scripts/verify-package.mjs",
60
+ "gate:consumers": "node scripts/check-consumer-version-floor.mjs",
61
+ "prepublishOnly": "pnpm run clean && pnpm run build && pnpm run verify:package"
57
62
  },
58
63
  "keywords": [
59
64
  "sentinel",