@nexartis/sentinel-sdk 1.6.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 (55) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +215 -0
  3. package/dist/core/auth-utils.d.ts +31 -0
  4. package/dist/core/auth-utils.d.ts.map +1 -0
  5. package/dist/core/auth-utils.js +128 -0
  6. package/dist/core/auth-utils.js.map +1 -0
  7. package/dist/core/client.d.ts +22 -0
  8. package/dist/core/client.d.ts.map +1 -0
  9. package/dist/core/client.js +137 -0
  10. package/dist/core/client.js.map +1 -0
  11. package/dist/core/errors.d.ts +47 -0
  12. package/dist/core/errors.d.ts.map +1 -0
  13. package/dist/core/errors.js +69 -0
  14. package/dist/core/errors.js.map +1 -0
  15. package/dist/core/index.d.ts +8 -0
  16. package/dist/core/index.d.ts.map +1 -0
  17. package/dist/core/index.js +6 -0
  18. package/dist/core/index.js.map +1 -0
  19. package/dist/core/logger.d.ts +30 -0
  20. package/dist/core/logger.d.ts.map +1 -0
  21. package/dist/core/logger.js +54 -0
  22. package/dist/core/logger.js.map +1 -0
  23. package/dist/core/types.d.ts +269 -0
  24. package/dist/core/types.d.ts.map +1 -0
  25. package/dist/core/types.js +2 -0
  26. package/dist/core/types.js.map +1 -0
  27. package/dist/sveltekit/client/index.d.ts +36 -0
  28. package/dist/sveltekit/client/index.d.ts.map +1 -0
  29. package/dist/sveltekit/client/index.js +121 -0
  30. package/dist/sveltekit/client/index.js.map +1 -0
  31. package/dist/sveltekit/cookies.d.ts +54 -0
  32. package/dist/sveltekit/cookies.d.ts.map +1 -0
  33. package/dist/sveltekit/cookies.js +64 -0
  34. package/dist/sveltekit/cookies.js.map +1 -0
  35. package/dist/sveltekit/handle.d.ts +20 -0
  36. package/dist/sveltekit/handle.d.ts.map +1 -0
  37. package/dist/sveltekit/handle.js +146 -0
  38. package/dist/sveltekit/handle.js.map +1 -0
  39. package/dist/sveltekit/handlers/index.d.ts +29 -0
  40. package/dist/sveltekit/handlers/index.d.ts.map +1 -0
  41. package/dist/sveltekit/handlers/index.js +536 -0
  42. package/dist/sveltekit/handlers/index.js.map +1 -0
  43. package/dist/sveltekit/index.d.ts +6 -0
  44. package/dist/sveltekit/index.d.ts.map +1 -0
  45. package/dist/sveltekit/index.js +5 -0
  46. package/dist/sveltekit/index.js.map +1 -0
  47. package/dist/sveltekit/pages/index.d.ts +68 -0
  48. package/dist/sveltekit/pages/index.d.ts.map +1 -0
  49. package/dist/sveltekit/pages/index.js +207 -0
  50. package/dist/sveltekit/pages/index.js.map +1 -0
  51. package/dist/sveltekit/utils.d.ts +17 -0
  52. package/dist/sveltekit/utils.d.ts.map +1 -0
  53. package/dist/sveltekit/utils.js +34 -0
  54. package/dist/sveltekit/utils.js.map +1 -0
  55. package/package.json +99 -0
@@ -0,0 +1,68 @@
1
+ export interface MagicLinkResult {
2
+ success: boolean;
3
+ crossBrowser?: boolean;
4
+ }
5
+ /**
6
+ * Handles the magic link page load.
7
+ * TWO-STEP process: completePasswordless() → claimTokens()
8
+ *
9
+ * Usage in +page.server.ts:
10
+ * ```typescript
11
+ * import { handleMagicLink } from '@nexartis/sentinel-sdk/sveltekit/pages';
12
+ *
13
+ * export const load: PageServerLoad = async (event) => {
14
+ * return handleMagicLink(event);
15
+ * };
16
+ * ```
17
+ */
18
+ export declare function handleMagicLink(event: {
19
+ url: URL;
20
+ fetch: typeof globalThis.fetch;
21
+ cookies: any;
22
+ platform?: any;
23
+ }): Promise<never>;
24
+ /**
25
+ * Handles the email verification page load.
26
+ *
27
+ * **Breaking change (v1.1):** Now requires `cookies` on the event object
28
+ * (previously only needed `url`, `fetch`, `platform`).
29
+ *
30
+ * Usage in +page.server.ts:
31
+ * ```typescript
32
+ * import { handleVerifyEmail } from '@nexartis/sentinel-sdk/sveltekit/pages';
33
+ *
34
+ * export const load: PageServerLoad = async (event) => {
35
+ * return handleVerifyEmail(event);
36
+ * };
37
+ * ```
38
+ */
39
+ export declare function handleVerifyEmail(event: {
40
+ url: URL;
41
+ fetch: typeof globalThis.fetch;
42
+ cookies: any;
43
+ platform?: any;
44
+ }): Promise<never>;
45
+ /**
46
+ * Handles the verified intermediate page load.
47
+ * Reads tokens from short-lived httpOnly cookies (set by handleVerifyEmail),
48
+ * validates the access token, then establishes the session.
49
+ *
50
+ * **Breaking change (v1.1):** Now requires `fetch` and `platform` on the event
51
+ * object (previously only needed `url` and `cookies`) for server-side token validation.
52
+ *
53
+ * Usage in +page.server.ts:
54
+ * ```typescript
55
+ * import { handleVerified } from '@nexartis/sentinel-sdk/sveltekit/pages';
56
+ *
57
+ * export const load: PageServerLoad = async (event) => {
58
+ * return handleVerified(event);
59
+ * };
60
+ * ```
61
+ */
62
+ export declare function handleVerified(event: {
63
+ url: URL;
64
+ cookies: any;
65
+ fetch: typeof globalThis.fetch;
66
+ platform?: any;
67
+ }): Promise<never>;
68
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sveltekit/pages/index.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC5C,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CAwDjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC9C,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CA2CjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC3C,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,GAAG,CAAC;IACb,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC;CACf,GAAG,OAAO,CAAC,KAAK,CAAC,CAgDjB"}
@@ -0,0 +1,207 @@
1
+ // src/sveltekit/pages/index.ts
2
+ import { redirect } from '@sveltejs/kit';
3
+ import { SentinelClient } from '../../core/client.js';
4
+ import { setSessionCookies, COOKIE_NAMES, COOKIE_OPTIONS } from '../cookies.js';
5
+ import { getConfigFromPlatform, clearLoginCookies } from '../utils.js';
6
+ import { validateToken } from '../../core/auth-utils.js';
7
+ /** Sanitise a redirect path to prevent open-redirect via env misconfiguration. */
8
+ function safeRedirectPath(raw) {
9
+ const path = (raw || '/').trim();
10
+ if (/^[/\\]{2}/.test(path) || /^[a-z][a-z0-9+.-]*:/i.test(path))
11
+ return '/';
12
+ return path.startsWith('/') ? path : '/';
13
+ }
14
+ /**
15
+ * Detects SvelteKit redirects using ALL THREE methods.
16
+ * CRITICAL: SvelteKit's Redirect object is NOT an instance of Response.
17
+ */
18
+ function isRedirectError(error) {
19
+ return (error instanceof Response ||
20
+ (error !== null && typeof error === 'object' && error.constructor?.name === 'Redirect') ||
21
+ (error !== null && typeof error === 'object' && 'status' in error && 'location' in error));
22
+ }
23
+ /**
24
+ * Handles the magic link page load.
25
+ * TWO-STEP process: completePasswordless() → claimTokens()
26
+ *
27
+ * Usage in +page.server.ts:
28
+ * ```typescript
29
+ * import { handleMagicLink } from '@nexartis/sentinel-sdk/sveltekit/pages';
30
+ *
31
+ * export const load: PageServerLoad = async (event) => {
32
+ * return handleMagicLink(event);
33
+ * };
34
+ * ```
35
+ */
36
+ export async function handleMagicLink(event) {
37
+ const magicToken = event.url.searchParams.get('token');
38
+ if (!magicToken) {
39
+ throw redirect(302, '/auth?error=invalid_link');
40
+ }
41
+ const config = getConfigFromPlatform(event.platform);
42
+ const sentinel = new SentinelClient(config, event.fetch);
43
+ try {
44
+ // Step 1: Complete the passwordless login (marks token as used)
45
+ const result = await sentinel.completePasswordless(magicToken);
46
+ if (result.code === 0 || result.success) {
47
+ // completePasswordless() only returns { code, message }
48
+ // It does NOT return tokens - those must be claimed separately
49
+ const transactionId = event.cookies.get('login_transaction_id');
50
+ const clientToken = event.cookies.get('login_client_token');
51
+ const rememberMe = event.cookies.get('login_remember_me') === 'true';
52
+ // Check if this is the same browser that initiated the login
53
+ if (!transactionId || !clientToken) {
54
+ // Different browser - show success message
55
+ throw redirect(302, '/auth?success=magic_link_clicked');
56
+ }
57
+ // Same browser - claim tokens immediately
58
+ // Step 2: Claim the tokens
59
+ const claimResult = await sentinel.claimTokens(transactionId, clientToken);
60
+ if (claimResult.code === 0 || claimResult.success) {
61
+ if (claimResult.access_token && claimResult.refresh_token) {
62
+ const postAuthRedirect = safeRedirectPath(event.platform?.env?.SENTINEL_POST_AUTH_REDIRECT);
63
+ setSessionCookies(event.cookies, claimResult.access_token, claimResult.refresh_token, rememberMe);
64
+ clearLoginCookies(event.cookies);
65
+ throw redirect(303, postAuthRedirect);
66
+ }
67
+ else {
68
+ throw redirect(302, '/auth?error=no_tokens');
69
+ }
70
+ }
71
+ else {
72
+ throw redirect(302, '/auth?error=claim_failed');
73
+ }
74
+ }
75
+ else {
76
+ throw redirect(302, '/auth?error=magic_link_failed');
77
+ }
78
+ }
79
+ catch (error) {
80
+ // CRITICAL: Detect redirects using ALL three methods
81
+ if (isRedirectError(error)) {
82
+ throw error; // Re-throw immediately
83
+ }
84
+ console.error('Magic link error:', error);
85
+ throw redirect(302, '/auth?error=magic_link_failed');
86
+ }
87
+ }
88
+ /**
89
+ * Handles the email verification page load.
90
+ *
91
+ * **Breaking change (v1.1):** Now requires `cookies` on the event object
92
+ * (previously only needed `url`, `fetch`, `platform`).
93
+ *
94
+ * Usage in +page.server.ts:
95
+ * ```typescript
96
+ * import { handleVerifyEmail } from '@nexartis/sentinel-sdk/sveltekit/pages';
97
+ *
98
+ * export const load: PageServerLoad = async (event) => {
99
+ * return handleVerifyEmail(event);
100
+ * };
101
+ * ```
102
+ */
103
+ export async function handleVerifyEmail(event) {
104
+ const token = event.url.searchParams.get('token');
105
+ if (!token) {
106
+ throw redirect(302, '/auth?error=invalid_link');
107
+ }
108
+ const config = getConfigFromPlatform(event.platform);
109
+ const sentinel = new SentinelClient(config, event.fetch);
110
+ try {
111
+ const result = await sentinel.verifyEmail(token);
112
+ if (result.code === 0 || result.success) {
113
+ if (result.access_token && result.refresh_token) {
114
+ // SEC-001 fix: Set tokens in short-lived httpOnly cookies (NOT URL params).
115
+ // These are consumed immediately by handleVerified on /auth/verified.
116
+ event.cookies.set(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, result.access_token, COOKIE_OPTIONS.VERIFY_TOKEN);
117
+ event.cookies.set(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.VERIFY_TOKEN);
118
+ throw redirect(303, '/auth/verified');
119
+ }
120
+ else {
121
+ // Success but no tokens — clear any stale verify cookies from prior attempts
122
+ event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
123
+ event.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
124
+ throw redirect(302, '/auth?verified=true');
125
+ }
126
+ }
127
+ else {
128
+ // Clear any stale verify cookies from prior attempts
129
+ event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
130
+ event.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
131
+ throw redirect(302, '/auth?error=verification_failed');
132
+ }
133
+ }
134
+ catch (error) {
135
+ if (isRedirectError(error)) {
136
+ throw error;
137
+ }
138
+ // Clear any stale verify cookies on error
139
+ event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
140
+ event.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
141
+ console.error('Email verification error:', error);
142
+ throw redirect(302, '/auth?error=verification_failed');
143
+ }
144
+ }
145
+ /**
146
+ * Handles the verified intermediate page load.
147
+ * Reads tokens from short-lived httpOnly cookies (set by handleVerifyEmail),
148
+ * validates the access token, then establishes the session.
149
+ *
150
+ * **Breaking change (v1.1):** Now requires `fetch` and `platform` on the event
151
+ * object (previously only needed `url` and `cookies`) for server-side token validation.
152
+ *
153
+ * Usage in +page.server.ts:
154
+ * ```typescript
155
+ * import { handleVerified } from '@nexartis/sentinel-sdk/sveltekit/pages';
156
+ *
157
+ * export const load: PageServerLoad = async (event) => {
158
+ * return handleVerified(event);
159
+ * };
160
+ * ```
161
+ */
162
+ export async function handleVerified(event) {
163
+ const accessToken = event.cookies.get(COOKIE_NAMES.VERIFY_ACCESS_TOKEN);
164
+ const refreshToken = event.cookies.get(COOKIE_NAMES.VERIFY_REFRESH_TOKEN);
165
+ // Sanitize redirect target to prevent open-redirect attacks.
166
+ // Only allow relative paths starting with a single '/'. Reject protocol-relative
167
+ // URLs ('//evil.com'), absolute URLs ('https://evil.com'), backslash variants
168
+ // ('/\evil.com' which some user agents normalise to '//evil.com'), percent-encoded
169
+ // backslashes ('%5c' / '%5C' which browsers may decode and normalise the same way),
170
+ // and bare strings.
171
+ const rawRedirect = event.url.searchParams.get('redirect') || '/';
172
+ const redirectTo = (rawRedirect.startsWith('/') && !rawRedirect.startsWith('//') && !rawRedirect.includes('\\') && !rawRedirect.toLowerCase().includes('%5c')) ? rawRedirect : '/';
173
+ if (!accessToken || !refreshToken) {
174
+ // Clear any partial verify cookies to avoid stale state within the TTL
175
+ event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
176
+ event.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
177
+ throw redirect(302, '/auth?error=invalid_verification');
178
+ }
179
+ // SEC-002 fix: Validate the token before consuming the cookies.
180
+ // On transient network/API failures, leave cookies intact so the user can
181
+ // retry within the 60s TTL without re-running the entire verification flow.
182
+ const config = getConfigFromPlatform(event.platform);
183
+ let decoded;
184
+ try {
185
+ decoded = await validateToken(accessToken, event.fetch, config);
186
+ }
187
+ catch {
188
+ // Transient error — preserve cookies for retry
189
+ throw redirect(302, '/auth?error=verification_error');
190
+ }
191
+ if (!decoded) {
192
+ // Definitively invalid token — clear cookies, no point retrying
193
+ event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
194
+ event.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
195
+ throw redirect(302, '/auth?error=invalid_verification');
196
+ }
197
+ // Consume single-use verification cookies after successful validation
198
+ event.cookies.delete(COOKIE_NAMES.VERIFY_ACCESS_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
199
+ event.cookies.delete(COOKIE_NAMES.VERIFY_REFRESH_TOKEN, { path: COOKIE_OPTIONS.VERIFY_TOKEN.path });
200
+ // rememberMe=true is intentional: email verification proves device trust.
201
+ // The original registration form's rememberMe preference cannot be reliably
202
+ // carried through the async email verification flow. (ARCH-004)
203
+ setSessionCookies(event.cookies, accessToken, refreshToken, true);
204
+ // Redirect to destination
205
+ throw redirect(303, redirectTo);
206
+ }
207
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,17 @@
1
+ import type { SentinelConfig } from '../core/types.js';
2
+ /**
3
+ * Build SentinelConfig from platform environment variables.
4
+ * Shared across handlers and pages modules.
5
+ *
6
+ * Note: baseUrl may be empty here — that's expected. In Workers for Platforms
7
+ * deployments the cubi's createAuthHandle() (in hooks.server.ts) detects the
8
+ * request URL and writes it to platform.env.VITE_BASE_URL before the SDK's
9
+ * createSentinelHandle() runs. The SDK handle itself only reads the env var
10
+ * into a local config object and does NOT mutate platform.env.
11
+ */
12
+ export declare function getConfigFromPlatform(platform?: any): SentinelConfig;
13
+ /**
14
+ * Clear all login-flow cookies (transaction, client token, remember me, claim).
15
+ */
16
+ export declare function clearLoginCookies(cookies: any): void;
17
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/sveltekit/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,CAAC,EAAE,GAAG,GAAG,cAAc,CAepE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAKpD"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Build SentinelConfig from platform environment variables.
3
+ * Shared across handlers and pages modules.
4
+ *
5
+ * Note: baseUrl may be empty here — that's expected. In Workers for Platforms
6
+ * deployments the cubi's createAuthHandle() (in hooks.server.ts) detects the
7
+ * request URL and writes it to platform.env.VITE_BASE_URL before the SDK's
8
+ * createSentinelHandle() runs. The SDK handle itself only reads the env var
9
+ * into a local config object and does NOT mutate platform.env.
10
+ */
11
+ export function getConfigFromPlatform(platform) {
12
+ let apiUrl = platform?.env?.SENTINEL_API_URL || '';
13
+ const baseUrl = platform?.env?.VITE_BASE_URL || '';
14
+ if (!apiUrl) {
15
+ throw new Error('SENTINEL_API_URL must be configured');
16
+ }
17
+ // Normalize: strip trailing slashes, then append /api if not already present
18
+ while (apiUrl.endsWith('/'))
19
+ apiUrl = apiUrl.slice(0, -1);
20
+ if (!apiUrl.endsWith('/api')) {
21
+ apiUrl = apiUrl + '/api';
22
+ }
23
+ return { apiUrl, baseUrl };
24
+ }
25
+ /**
26
+ * Clear all login-flow cookies (transaction, client token, remember me, claim).
27
+ */
28
+ export function clearLoginCookies(cookies) {
29
+ cookies.delete('claim_transaction_id', { path: '/' });
30
+ cookies.delete('login_transaction_id', { path: '/' });
31
+ cookies.delete('login_client_token', { path: '/' });
32
+ cookies.delete('login_remember_me', { path: '/' });
33
+ }
34
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/sveltekit/utils.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAc;IACnD,IAAI,MAAM,GAAG,QAAQ,EAAE,GAAG,EAAE,gBAAgB,IAAI,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,QAAQ,EAAE,GAAG,EAAE,aAAa,IAAI,EAAE,CAAC;IAEnD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACxD,CAAC;IAED,6EAA6E;IAC7E,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAY;IAC7C,OAAO,CAAC,MAAM,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACtD,OAAO,CAAC,MAAM,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACtD,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AACpD,CAAC","sourcesContent":["// src/sveltekit/utils.ts\nimport type { SentinelConfig } from '../core/types.js';\n\n/**\n * Build SentinelConfig from platform environment variables.\n * Shared across handlers and pages modules.\n *\n * Note: baseUrl may be empty here — that's expected. In Workers for Platforms\n * deployments the cubi's createAuthHandle() (in hooks.server.ts) detects the\n * request URL and writes it to platform.env.VITE_BASE_URL before the SDK's\n * createSentinelHandle() runs. The SDK handle itself only reads the env var\n * into a local config object and does NOT mutate platform.env.\n */\nexport function getConfigFromPlatform(platform?: any): SentinelConfig {\n\tlet apiUrl = platform?.env?.SENTINEL_API_URL || '';\n\tconst baseUrl = platform?.env?.VITE_BASE_URL || '';\n\n\tif (!apiUrl) {\n\t\tthrow new Error('SENTINEL_API_URL must be configured');\n\t}\n\n\t// Normalize: strip trailing slashes, then append /api if not already present\n\twhile (apiUrl.endsWith('/')) apiUrl = apiUrl.slice(0, -1);\n\tif (!apiUrl.endsWith('/api')) {\n\t\tapiUrl = apiUrl + '/api';\n\t}\n\n\treturn { apiUrl, baseUrl };\n}\n\n/**\n * Clear all login-flow cookies (transaction, client token, remember me, claim).\n */\nexport function clearLoginCookies(cookies: any): void {\n\tcookies.delete('claim_transaction_id', { path: '/' });\n\tcookies.delete('login_transaction_id', { path: '/' });\n\tcookies.delete('login_client_token', { path: '/' });\n\tcookies.delete('login_remember_me', { path: '/' });\n}\n\n"]}
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@nexartis/sentinel-sdk",
3
+ "version": "1.6.0",
4
+ "description": "Passwordless magic-link authentication SDK for SvelteKit — client for the hosted Nexartis Sentinel auth backend.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Nexartis/sentinel-sdk.git"
8
+ },
9
+ "homepage": "https://github.com/Nexartis/sentinel-sdk#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/Nexartis/sentinel-sdk/issues"
12
+ },
13
+ "publishConfig": {
14
+ "registry": "https://registry.npmjs.org",
15
+ "access": "public"
16
+ },
17
+ "type": "module",
18
+ "main": "./dist/core/index.js",
19
+ "types": "./dist/core/index.d.ts",
20
+ "exports": {
21
+ "./core": {
22
+ "types": "./dist/core/index.d.ts",
23
+ "import": "./dist/core/index.js"
24
+ },
25
+ "./sveltekit": {
26
+ "types": "./dist/sveltekit/index.d.ts",
27
+ "import": "./dist/sveltekit/index.js"
28
+ },
29
+ "./sveltekit/handlers": {
30
+ "types": "./dist/sveltekit/handlers/index.d.ts",
31
+ "import": "./dist/sveltekit/handlers/index.js"
32
+ },
33
+ "./sveltekit/client": {
34
+ "types": "./dist/sveltekit/client/index.d.ts",
35
+ "import": "./dist/sveltekit/client/index.js"
36
+ },
37
+ "./sveltekit/pages": {
38
+ "types": "./dist/sveltekit/pages/index.d.ts",
39
+ "import": "./dist/sveltekit/pages/index.js"
40
+ }
41
+ },
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "check": "tsc --noEmit",
48
+ "dev": "tsc --watch",
49
+ "lint": "pnpm run check",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest",
52
+ "validate": "pnpm run check && pnpm run test && pnpm run build",
53
+ "prepublishOnly": "pnpm run build"
54
+ },
55
+ "keywords": [
56
+ "sentinel",
57
+ "auth",
58
+ "authentication",
59
+ "sveltekit",
60
+ "svelte",
61
+ "passwordless",
62
+ "magic-link",
63
+ "jwt",
64
+ "nexartis"
65
+ ],
66
+ "author": "Nexartis",
67
+ "license": "Apache-2.0",
68
+ "peerDependencies": {
69
+ "@sveltejs/kit": "^2.0.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@sveltejs/kit": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "devDependencies": {
77
+ "@sveltejs/kit": "^2.66.0",
78
+ "svelte": "^5.56.3",
79
+ "typescript": "^5.0.0",
80
+ "vite": "^7.3.5",
81
+ "vitest": "^4.1.9"
82
+ },
83
+ "pnpm": {
84
+ "overrides": {
85
+ "cookie": "^1.0.0",
86
+ "svelte": ">=5.56.3",
87
+ "vite": "^7.3.5",
88
+ "esbuild": ">=0.28.1",
89
+ "picomatch": "^4.0.4",
90
+ "devalue": ">=5.8.1",
91
+ "postcss": ">=8.5.10"
92
+ }
93
+ },
94
+ "engines": {
95
+ "node": ">=20.0.0",
96
+ "pnpm": ">=10.0.0"
97
+ },
98
+ "packageManager": "pnpm@10.28.1"
99
+ }