@sylphx/sdk 0.15.1 → 0.15.3
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/dist/index.d.ts +7 -0
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.d.ts +278 -264
- package/dist/nextjs/index.mjs +247 -215
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.d.ts +11 -5
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/nextjs/index.mjs
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
1
|
// src/nextjs/middleware.ts
|
|
2
2
|
import { NextResponse } from "next/server";
|
|
3
3
|
|
|
4
|
-
// src/constants.ts
|
|
5
|
-
var ENV_URL = "SYLPHX_URL";
|
|
6
|
-
var ENV_PUBLIC_URL = "NEXT_PUBLIC_SYLPHX_URL";
|
|
7
|
-
var ENV_SECRET_URL = "SYLPHX_SECRET_URL";
|
|
8
|
-
var SDK_API_PATH = `/v1`;
|
|
9
|
-
var DEFAULT_SDK_API_HOST = "api.sylphx.com";
|
|
10
|
-
function detectSdkPlatform() {
|
|
11
|
-
if (typeof window !== "undefined") return "browser";
|
|
12
|
-
const runtimeGlobal = globalThis;
|
|
13
|
-
if (typeof runtimeGlobal.EdgeRuntime !== "undefined") return "edge";
|
|
14
|
-
return "node";
|
|
15
|
-
}
|
|
16
|
-
var SDK_PLATFORM = detectSdkPlatform();
|
|
17
|
-
var TOKEN_EXPIRY_BUFFER_MS = 3e4;
|
|
18
|
-
var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
|
|
19
|
-
var SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1e3;
|
|
20
|
-
var REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
21
|
-
var FLAGS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
22
|
-
var FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1e3;
|
|
23
|
-
var ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
24
|
-
var WEBHOOK_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
25
|
-
var WEBHOOK_CLOCK_SKEW_MS = 30 * 1e3;
|
|
26
|
-
var PKCE_CODE_TTL_MS = 10 * 60 * 1e3;
|
|
27
|
-
var JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
28
|
-
var SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1e3;
|
|
29
|
-
var FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1e3;
|
|
30
|
-
var CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
31
|
-
var STALE_TIME_FREQUENT_MS = 60 * 1e3;
|
|
32
|
-
var STALE_TIME_MODERATE_MS = 2 * 60 * 1e3;
|
|
33
|
-
var STALE_TIME_STABLE_MS = 5 * 60 * 1e3;
|
|
34
|
-
var STALE_TIME_STATS_MS = 30 * 1e3;
|
|
35
|
-
var NEW_USER_THRESHOLD_MS = 60 * 1e3;
|
|
36
|
-
var STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
37
|
-
var STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
38
|
-
var STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024;
|
|
39
|
-
var STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
|
40
|
-
var JWK_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
41
|
-
var ETAG_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
42
|
-
|
|
43
4
|
// src/key-validation.ts
|
|
44
5
|
var PUBLIC_KEY_PATTERN = /^pk_(dev|stg|prod|prev)(?:_[a-z0-9]{12})?_[a-f0-9]{32}$/;
|
|
45
6
|
var APP_ID_PATTERN = /^(app|pk)_(dev|stg|prod|prev)_[a-z0-9_-]+$/;
|
|
@@ -196,6 +157,47 @@ function getCookieNamespace(secretKey) {
|
|
|
196
157
|
|
|
197
158
|
// src/nextjs/cookies.ts
|
|
198
159
|
import { cookies } from "next/headers";
|
|
160
|
+
|
|
161
|
+
// src/constants.ts
|
|
162
|
+
var ENV_URL = "SYLPHX_URL";
|
|
163
|
+
var ENV_PUBLIC_URL = "NEXT_PUBLIC_SYLPHX_URL";
|
|
164
|
+
var ENV_SECRET_URL = "SYLPHX_SECRET_URL";
|
|
165
|
+
var SDK_API_PATH = `/v1`;
|
|
166
|
+
var DEFAULT_SDK_API_HOST = "api.sylphx.com";
|
|
167
|
+
function detectSdkPlatform() {
|
|
168
|
+
if (typeof window !== "undefined") return "browser";
|
|
169
|
+
const runtimeGlobal = globalThis;
|
|
170
|
+
if (typeof runtimeGlobal.EdgeRuntime !== "undefined") return "edge";
|
|
171
|
+
return "node";
|
|
172
|
+
}
|
|
173
|
+
var SDK_PLATFORM = detectSdkPlatform();
|
|
174
|
+
var TOKEN_EXPIRY_BUFFER_MS = 3e4;
|
|
175
|
+
var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
|
|
176
|
+
var SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1e3;
|
|
177
|
+
var REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
178
|
+
var FLAGS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
179
|
+
var FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1e3;
|
|
180
|
+
var ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
181
|
+
var WEBHOOK_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
182
|
+
var WEBHOOK_CLOCK_SKEW_MS = 30 * 1e3;
|
|
183
|
+
var PKCE_CODE_TTL_MS = 10 * 60 * 1e3;
|
|
184
|
+
var JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
185
|
+
var SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1e3;
|
|
186
|
+
var FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1e3;
|
|
187
|
+
var CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
188
|
+
var STALE_TIME_FREQUENT_MS = 60 * 1e3;
|
|
189
|
+
var STALE_TIME_MODERATE_MS = 2 * 60 * 1e3;
|
|
190
|
+
var STALE_TIME_STABLE_MS = 5 * 60 * 1e3;
|
|
191
|
+
var STALE_TIME_STATS_MS = 30 * 1e3;
|
|
192
|
+
var NEW_USER_THRESHOLD_MS = 60 * 1e3;
|
|
193
|
+
var STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
194
|
+
var STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
195
|
+
var STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024;
|
|
196
|
+
var STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
|
197
|
+
var JWK_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
198
|
+
var ETAG_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
199
|
+
|
|
200
|
+
// src/nextjs/cookies.ts
|
|
199
201
|
function getCookieNames(namespace) {
|
|
200
202
|
return {
|
|
201
203
|
/** HttpOnly JWT access token (5 min) */
|
|
@@ -230,6 +232,26 @@ var ACTIVE_ORG_COOKIE_OPTIONS = {
|
|
|
230
232
|
...SECURE_COOKIE_OPTIONS,
|
|
231
233
|
httpOnly: true
|
|
232
234
|
};
|
|
235
|
+
function decodeCookieValue(value) {
|
|
236
|
+
try {
|
|
237
|
+
return decodeURIComponent(value);
|
|
238
|
+
} catch {
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function readCookieValueFromHeader(cookieHeader, name) {
|
|
243
|
+
if (!cookieHeader) return null;
|
|
244
|
+
let value = null;
|
|
245
|
+
for (const cookie of cookieHeader.split(";")) {
|
|
246
|
+
const trimmed = cookie.trim();
|
|
247
|
+
const eq = trimmed.indexOf("=");
|
|
248
|
+
if (eq === -1) continue;
|
|
249
|
+
if (trimmed.slice(0, eq) === name) {
|
|
250
|
+
value = decodeCookieValue(trimmed.slice(eq + 1));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
233
255
|
async function getAuthCookies(namespace) {
|
|
234
256
|
const cookieStore = await cookies();
|
|
235
257
|
const names = getCookieNames(namespace);
|
|
@@ -326,6 +348,163 @@ function parseUserCookie(value) {
|
|
|
326
348
|
}
|
|
327
349
|
}
|
|
328
350
|
|
|
351
|
+
// src/nextjs/middleware-helpers.ts
|
|
352
|
+
var OAUTH_PKCE_TTL_SECONDS = 10 * 60;
|
|
353
|
+
var OAUTH_PKCE_TTL_MS = OAUTH_PKCE_TTL_SECONDS * 1e3;
|
|
354
|
+
var DEFAULT_BAAS_PREFIX = "/sylphx";
|
|
355
|
+
var BAAS_PROXY_AUTH_REQUIRED_PATHS = ["/challenge/", "/security/"];
|
|
356
|
+
var BAAS_PROXY_PUBLIC_PATHS = ["/app"];
|
|
357
|
+
var BODYLESS_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
358
|
+
var RESPONSE_HEADER_ALLOWLIST = [
|
|
359
|
+
"cache-control",
|
|
360
|
+
"content-language",
|
|
361
|
+
"content-type",
|
|
362
|
+
"etag",
|
|
363
|
+
"expires",
|
|
364
|
+
"last-modified",
|
|
365
|
+
"retry-after"
|
|
366
|
+
];
|
|
367
|
+
function isTokenResponse(data) {
|
|
368
|
+
return typeof data === "object" && data !== null && "accessToken" in data && "refreshToken" in data && "user" in data && typeof data.accessToken === "string" && typeof data.refreshToken === "string";
|
|
369
|
+
}
|
|
370
|
+
function decodeJwtPayload(token) {
|
|
371
|
+
try {
|
|
372
|
+
const parts = token.split(".");
|
|
373
|
+
if (parts.length !== 3) return null;
|
|
374
|
+
const payload = parts[1];
|
|
375
|
+
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
376
|
+
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
|
|
377
|
+
const jsonPayload = atob(padded);
|
|
378
|
+
return JSON.parse(jsonPayload);
|
|
379
|
+
} catch {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function isTokenExpired(token) {
|
|
384
|
+
const payload = decodeJwtPayload(token);
|
|
385
|
+
if (!payload?.exp) return true;
|
|
386
|
+
return payload.exp * 1e3 < Date.now() + TOKEN_EXPIRY_BUFFER_MS;
|
|
387
|
+
}
|
|
388
|
+
function matchesPattern(pathname, pattern) {
|
|
389
|
+
if (pattern === pathname) return true;
|
|
390
|
+
if (pattern.endsWith("/*")) {
|
|
391
|
+
const base = pattern.slice(0, -2);
|
|
392
|
+
return pathname === base || pathname.startsWith(`${base}/`);
|
|
393
|
+
}
|
|
394
|
+
if (pattern.endsWith("/**")) {
|
|
395
|
+
const base = pattern.slice(0, -3);
|
|
396
|
+
return pathname === base || pathname.startsWith(`${base}/`);
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
function matchesAny(pathname, patterns) {
|
|
401
|
+
return patterns.some((p) => matchesPattern(pathname, p));
|
|
402
|
+
}
|
|
403
|
+
function normalizeRoutePrefix(prefix) {
|
|
404
|
+
const normalized = prefix.trim().replace(/\/+$/u, "");
|
|
405
|
+
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
406
|
+
}
|
|
407
|
+
function stripRoutePrefix(pathname, prefix) {
|
|
408
|
+
if (pathname === prefix) return "/";
|
|
409
|
+
if (!pathname.startsWith(`${prefix}/`)) return null;
|
|
410
|
+
return pathname.slice(prefix.length);
|
|
411
|
+
}
|
|
412
|
+
function isPublicBaasProxyPath(pathname) {
|
|
413
|
+
return BAAS_PROXY_PUBLIC_PATHS.some((path) => pathname === path);
|
|
414
|
+
}
|
|
415
|
+
function isAuthenticatedBaasProxyPath(pathname) {
|
|
416
|
+
return BAAS_PROXY_AUTH_REQUIRED_PATHS.some((prefix) => pathname.startsWith(prefix));
|
|
417
|
+
}
|
|
418
|
+
function isAllowedBaasProxyPath(pathname) {
|
|
419
|
+
return isPublicBaasProxyPath(pathname) || isAuthenticatedBaasProxyPath(pathname);
|
|
420
|
+
}
|
|
421
|
+
function copyRequestHeader(source, target, name) {
|
|
422
|
+
const value = source.get(name);
|
|
423
|
+
if (value) target.set(name, value);
|
|
424
|
+
}
|
|
425
|
+
function buildUpstreamProxyHeaders(request, ctx, sessionToken) {
|
|
426
|
+
const headers = new Headers();
|
|
427
|
+
copyRequestHeader(request.headers, headers, "accept");
|
|
428
|
+
copyRequestHeader(request.headers, headers, "content-type");
|
|
429
|
+
copyRequestHeader(request.headers, headers, "user-agent");
|
|
430
|
+
copyRequestHeader(request.headers, headers, "x-correlation-id");
|
|
431
|
+
headers.set("x-app-secret", ctx.secretKey);
|
|
432
|
+
if (sessionToken) headers.set("authorization", `Bearer ${sessionToken}`);
|
|
433
|
+
return headers;
|
|
434
|
+
}
|
|
435
|
+
function copyResponseHeaders(source) {
|
|
436
|
+
const headers = new Headers();
|
|
437
|
+
for (const name of RESPONSE_HEADER_ALLOWLIST) {
|
|
438
|
+
const value = source.get(name);
|
|
439
|
+
if (value) headers.set(name, value);
|
|
440
|
+
}
|
|
441
|
+
return headers;
|
|
442
|
+
}
|
|
443
|
+
function requestPathWithSearch(request) {
|
|
444
|
+
const { pathname, search } = request.nextUrl;
|
|
445
|
+
return `${pathname}${search}`;
|
|
446
|
+
}
|
|
447
|
+
function isSafeRelativeRedirectPath(value) {
|
|
448
|
+
return typeof value === "string" && value.startsWith("/") && !value.startsWith("//");
|
|
449
|
+
}
|
|
450
|
+
function resolveSafeRelativeRedirectPath(value, fallback) {
|
|
451
|
+
if (isSafeRelativeRedirectPath(value)) return value;
|
|
452
|
+
if (isSafeRelativeRedirectPath(fallback)) return fallback;
|
|
453
|
+
return "/";
|
|
454
|
+
}
|
|
455
|
+
function resolveSameOriginUrl(request, value, fallbackPath) {
|
|
456
|
+
if (!value) return new URL(fallbackPath, request.url).toString();
|
|
457
|
+
if (isSafeRelativeRedirectPath(value)) {
|
|
458
|
+
const url = new URL(value, request.url);
|
|
459
|
+
if (url.hash) return null;
|
|
460
|
+
return url.toString();
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const url = new URL(value);
|
|
464
|
+
if (url.origin !== new URL(request.url).origin) return null;
|
|
465
|
+
if (url.hash) return null;
|
|
466
|
+
return url.toString();
|
|
467
|
+
} catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function normalizeAppUrl(value) {
|
|
472
|
+
if (!value?.trim()) return null;
|
|
473
|
+
try {
|
|
474
|
+
const url = new URL(value);
|
|
475
|
+
return url.origin;
|
|
476
|
+
} catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function resolveOAuthCallbackUrl(request, ctx) {
|
|
481
|
+
const origin = ctx.config.appUrl ?? new URL(request.url).origin;
|
|
482
|
+
return new URL(`${ctx.config.authPrefix}/callback`, origin);
|
|
483
|
+
}
|
|
484
|
+
function bytesToBase64Url(bytes) {
|
|
485
|
+
let binary = "";
|
|
486
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
487
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
|
|
488
|
+
}
|
|
489
|
+
function randomBase64Url(byteLength) {
|
|
490
|
+
const bytes = new Uint8Array(byteLength);
|
|
491
|
+
crypto.getRandomValues(bytes);
|
|
492
|
+
return bytesToBase64Url(bytes);
|
|
493
|
+
}
|
|
494
|
+
async function sha256Base64Url(value) {
|
|
495
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
496
|
+
return bytesToBase64Url(new Uint8Array(digest));
|
|
497
|
+
}
|
|
498
|
+
async function parseJsonObject(request) {
|
|
499
|
+
try {
|
|
500
|
+
const body = await request.json();
|
|
501
|
+
if (typeof body === "object" && body !== null && !Array.isArray(body)) return body;
|
|
502
|
+
return null;
|
|
503
|
+
} catch {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
329
508
|
// src/connection-url.ts
|
|
330
509
|
var SYLPHX_PROTOCOL = "sylphx:";
|
|
331
510
|
var DEFAULT_VERSION = "v1";
|
|
@@ -482,161 +661,6 @@ function resolveNextjsPublishableKey(options) {
|
|
|
482
661
|
}
|
|
483
662
|
|
|
484
663
|
// src/nextjs/middleware.ts
|
|
485
|
-
var OAUTH_PKCE_TTL_SECONDS = 10 * 60;
|
|
486
|
-
var OAUTH_PKCE_TTL_MS = OAUTH_PKCE_TTL_SECONDS * 1e3;
|
|
487
|
-
var DEFAULT_BAAS_PREFIX = "/sylphx";
|
|
488
|
-
var BAAS_PROXY_AUTH_REQUIRED_PATHS = ["/challenge/", "/security/"];
|
|
489
|
-
var BAAS_PROXY_PUBLIC_PATHS = ["/app"];
|
|
490
|
-
var BODYLESS_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
491
|
-
var RESPONSE_HEADER_ALLOWLIST = [
|
|
492
|
-
"cache-control",
|
|
493
|
-
"content-language",
|
|
494
|
-
"content-type",
|
|
495
|
-
"etag",
|
|
496
|
-
"expires",
|
|
497
|
-
"last-modified",
|
|
498
|
-
"retry-after"
|
|
499
|
-
];
|
|
500
|
-
function isTokenResponse(data) {
|
|
501
|
-
return typeof data === "object" && data !== null && "accessToken" in data && "refreshToken" in data && "user" in data && typeof data.accessToken === "string" && typeof data.refreshToken === "string";
|
|
502
|
-
}
|
|
503
|
-
function decodeJwtPayload(token) {
|
|
504
|
-
try {
|
|
505
|
-
const parts = token.split(".");
|
|
506
|
-
if (parts.length !== 3) return null;
|
|
507
|
-
const payload = parts[1];
|
|
508
|
-
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
509
|
-
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
|
|
510
|
-
const jsonPayload = atob(padded);
|
|
511
|
-
return JSON.parse(jsonPayload);
|
|
512
|
-
} catch {
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
function isTokenExpired(token) {
|
|
517
|
-
const payload = decodeJwtPayload(token);
|
|
518
|
-
if (!payload?.exp) return true;
|
|
519
|
-
return payload.exp * 1e3 < Date.now() + TOKEN_EXPIRY_BUFFER_MS;
|
|
520
|
-
}
|
|
521
|
-
function matchesPattern(pathname, pattern) {
|
|
522
|
-
if (pattern === pathname) return true;
|
|
523
|
-
if (pattern.endsWith("/*")) {
|
|
524
|
-
const base = pattern.slice(0, -2);
|
|
525
|
-
return pathname === base || pathname.startsWith(`${base}/`);
|
|
526
|
-
}
|
|
527
|
-
if (pattern.endsWith("/**")) {
|
|
528
|
-
const base = pattern.slice(0, -3);
|
|
529
|
-
return pathname === base || pathname.startsWith(`${base}/`);
|
|
530
|
-
}
|
|
531
|
-
return false;
|
|
532
|
-
}
|
|
533
|
-
function matchesAny(pathname, patterns) {
|
|
534
|
-
return patterns.some((p) => matchesPattern(pathname, p));
|
|
535
|
-
}
|
|
536
|
-
function normalizeRoutePrefix(prefix) {
|
|
537
|
-
const normalized = prefix.trim().replace(/\/+$/u, "");
|
|
538
|
-
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
539
|
-
}
|
|
540
|
-
function stripRoutePrefix(pathname, prefix) {
|
|
541
|
-
if (pathname === prefix) return "/";
|
|
542
|
-
if (!pathname.startsWith(`${prefix}/`)) return null;
|
|
543
|
-
return pathname.slice(prefix.length);
|
|
544
|
-
}
|
|
545
|
-
function isPublicBaasProxyPath(pathname) {
|
|
546
|
-
return BAAS_PROXY_PUBLIC_PATHS.some((path) => pathname === path);
|
|
547
|
-
}
|
|
548
|
-
function isAuthenticatedBaasProxyPath(pathname) {
|
|
549
|
-
return BAAS_PROXY_AUTH_REQUIRED_PATHS.some((prefix) => pathname.startsWith(prefix));
|
|
550
|
-
}
|
|
551
|
-
function isAllowedBaasProxyPath(pathname) {
|
|
552
|
-
return isPublicBaasProxyPath(pathname) || isAuthenticatedBaasProxyPath(pathname);
|
|
553
|
-
}
|
|
554
|
-
function copyRequestHeader(source, target, name) {
|
|
555
|
-
const value = source.get(name);
|
|
556
|
-
if (value) target.set(name, value);
|
|
557
|
-
}
|
|
558
|
-
function buildUpstreamProxyHeaders(request, ctx, sessionToken) {
|
|
559
|
-
const headers = new Headers();
|
|
560
|
-
copyRequestHeader(request.headers, headers, "accept");
|
|
561
|
-
copyRequestHeader(request.headers, headers, "content-type");
|
|
562
|
-
copyRequestHeader(request.headers, headers, "user-agent");
|
|
563
|
-
copyRequestHeader(request.headers, headers, "x-correlation-id");
|
|
564
|
-
headers.set("x-app-secret", ctx.secretKey);
|
|
565
|
-
if (sessionToken) headers.set("authorization", `Bearer ${sessionToken}`);
|
|
566
|
-
return headers;
|
|
567
|
-
}
|
|
568
|
-
function copyResponseHeaders(source) {
|
|
569
|
-
const headers = new Headers();
|
|
570
|
-
for (const name of RESPONSE_HEADER_ALLOWLIST) {
|
|
571
|
-
const value = source.get(name);
|
|
572
|
-
if (value) headers.set(name, value);
|
|
573
|
-
}
|
|
574
|
-
return headers;
|
|
575
|
-
}
|
|
576
|
-
function requestPathWithSearch(request) {
|
|
577
|
-
const { pathname, search } = request.nextUrl;
|
|
578
|
-
return `${pathname}${search}`;
|
|
579
|
-
}
|
|
580
|
-
function isSafeRelativeRedirectPath(value) {
|
|
581
|
-
return typeof value === "string" && value.startsWith("/") && !value.startsWith("//");
|
|
582
|
-
}
|
|
583
|
-
function resolveSafeRelativeRedirectPath(value, fallback) {
|
|
584
|
-
if (isSafeRelativeRedirectPath(value)) return value;
|
|
585
|
-
if (isSafeRelativeRedirectPath(fallback)) return fallback;
|
|
586
|
-
return "/";
|
|
587
|
-
}
|
|
588
|
-
function resolveSameOriginUrl(request, value, fallbackPath) {
|
|
589
|
-
if (!value) return new URL(fallbackPath, request.url).toString();
|
|
590
|
-
if (isSafeRelativeRedirectPath(value)) {
|
|
591
|
-
const url = new URL(value, request.url);
|
|
592
|
-
if (url.hash) return null;
|
|
593
|
-
return url.toString();
|
|
594
|
-
}
|
|
595
|
-
try {
|
|
596
|
-
const url = new URL(value);
|
|
597
|
-
if (url.origin !== new URL(request.url).origin) return null;
|
|
598
|
-
if (url.hash) return null;
|
|
599
|
-
return url.toString();
|
|
600
|
-
} catch {
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
function normalizeAppUrl(value) {
|
|
605
|
-
if (!value?.trim()) return null;
|
|
606
|
-
try {
|
|
607
|
-
const url = new URL(value);
|
|
608
|
-
return url.origin;
|
|
609
|
-
} catch {
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
function resolveOAuthCallbackUrl(request, ctx) {
|
|
614
|
-
const origin = ctx.config.appUrl ?? new URL(request.url).origin;
|
|
615
|
-
return new URL(`${ctx.config.authPrefix}/callback`, origin);
|
|
616
|
-
}
|
|
617
|
-
function bytesToBase64Url(bytes) {
|
|
618
|
-
let binary = "";
|
|
619
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
620
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
|
|
621
|
-
}
|
|
622
|
-
function randomBase64Url(byteLength) {
|
|
623
|
-
const bytes = new Uint8Array(byteLength);
|
|
624
|
-
crypto.getRandomValues(bytes);
|
|
625
|
-
return bytesToBase64Url(bytes);
|
|
626
|
-
}
|
|
627
|
-
async function sha256Base64Url(value) {
|
|
628
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
629
|
-
return bytesToBase64Url(new Uint8Array(digest));
|
|
630
|
-
}
|
|
631
|
-
async function parseJsonObject(request) {
|
|
632
|
-
try {
|
|
633
|
-
const body = await request.json();
|
|
634
|
-
if (typeof body === "object" && body !== null && !Array.isArray(body)) return body;
|
|
635
|
-
return null;
|
|
636
|
-
} catch {
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
664
|
function getOAuthPkceCookieName(ctx) {
|
|
641
665
|
return `__${ctx.namespace}_oauth_pkce`;
|
|
642
666
|
}
|
|
@@ -670,6 +694,19 @@ function clearOAuthPkceCookie(response, ctx) {
|
|
|
670
694
|
path: ctx.config.authPrefix
|
|
671
695
|
});
|
|
672
696
|
}
|
|
697
|
+
function readRequestCookieValue(request, name) {
|
|
698
|
+
const values = request.cookies.getAll(name);
|
|
699
|
+
if (values.length > 0) {
|
|
700
|
+
const value = values[values.length - 1]?.value;
|
|
701
|
+
return value === void 0 ? void 0 : decodeCookieValue(value);
|
|
702
|
+
}
|
|
703
|
+
const cookie = request.cookies.get(name);
|
|
704
|
+
if (cookie) return decodeCookieValue(cookie.value);
|
|
705
|
+
return readCookieValueFromHeader(request.headers.get("cookie"), name) ?? void 0;
|
|
706
|
+
}
|
|
707
|
+
function hasRequestCookie(request, name) {
|
|
708
|
+
return readRequestCookieValue(request, name) !== void 0;
|
|
709
|
+
}
|
|
673
710
|
function isTwoFactorLoginResponse(data) {
|
|
674
711
|
return typeof data === "object" && data !== null && data.requiresTwoFactor === true && typeof data.userId === "string";
|
|
675
712
|
}
|
|
@@ -702,7 +739,7 @@ function setRestoredSessionCookies(response, ctx, restored) {
|
|
|
702
739
|
}
|
|
703
740
|
}
|
|
704
741
|
async function refreshSessionFromCookie(request, ctx, previousSessionToken) {
|
|
705
|
-
const refreshToken = request
|
|
742
|
+
const refreshToken = readRequestCookieValue(request, ctx.cookieNames.REFRESH);
|
|
706
743
|
if (!refreshToken) return null;
|
|
707
744
|
const refreshedTokens = await refreshTokens(refreshToken, ctx);
|
|
708
745
|
if (!refreshedTokens) return null;
|
|
@@ -799,8 +836,8 @@ async function handleSession(request, ctx) {
|
|
|
799
836
|
if (request.method !== "GET") {
|
|
800
837
|
return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
|
|
801
838
|
}
|
|
802
|
-
const sessionToken = request
|
|
803
|
-
const userCookie = parseUserCookie2(request
|
|
839
|
+
const sessionToken = readRequestCookieValue(request, ctx.cookieNames.SESSION);
|
|
840
|
+
const userCookie = parseUserCookie2(readRequestCookieValue(request, ctx.cookieNames.USER));
|
|
804
841
|
if (sessionToken && !isTokenExpired(sessionToken) && userCookie?.user) {
|
|
805
842
|
return NextResponse.json(sessionMetadataBody(sessionToken, userCookie.user));
|
|
806
843
|
}
|
|
@@ -813,7 +850,7 @@ async function handleSession(request, ctx) {
|
|
|
813
850
|
return response2;
|
|
814
851
|
}
|
|
815
852
|
const response = NextResponse.json({ success: true, session: null, user: null });
|
|
816
|
-
if (sessionToken || request
|
|
853
|
+
if (sessionToken || hasRequestCookie(request, ctx.cookieNames.REFRESH) || userCookie) {
|
|
817
854
|
clearAuthCookiesMiddleware(response, ctx.namespace);
|
|
818
855
|
}
|
|
819
856
|
return response;
|
|
@@ -999,7 +1036,7 @@ async function handleCallback(request, ctx) {
|
|
|
999
1036
|
}
|
|
1000
1037
|
async function handleSignOut(request, ctx) {
|
|
1001
1038
|
ctx.log("Signout");
|
|
1002
|
-
const refreshToken = request
|
|
1039
|
+
const refreshToken = readRequestCookieValue(request, ctx.cookieNames.REFRESH);
|
|
1003
1040
|
if (refreshToken) {
|
|
1004
1041
|
try {
|
|
1005
1042
|
await fetch(`${ctx.platformUrl}/v1/auth/revoke`, {
|
|
@@ -1021,7 +1058,7 @@ async function handleSignOut(request, ctx) {
|
|
|
1021
1058
|
}
|
|
1022
1059
|
async function handleToken(request, ctx) {
|
|
1023
1060
|
ctx.log("Token request");
|
|
1024
|
-
const sessionToken = request
|
|
1061
|
+
const sessionToken = readRequestCookieValue(request, ctx.cookieNames.SESSION);
|
|
1025
1062
|
if (sessionToken && !isTokenExpired(sessionToken)) {
|
|
1026
1063
|
ctx.log("Token returned");
|
|
1027
1064
|
return NextResponse.json({ accessToken: sessionToken });
|
|
@@ -1038,7 +1075,7 @@ async function handleToken(request, ctx) {
|
|
|
1038
1075
|
{ error: sessionToken ? "Session expired" : "Not authenticated", accessToken: null },
|
|
1039
1076
|
{ status: 401 }
|
|
1040
1077
|
);
|
|
1041
|
-
if (sessionToken || request
|
|
1078
|
+
if (sessionToken || hasRequestCookie(request, ctx.cookieNames.REFRESH)) {
|
|
1042
1079
|
clearAuthCookiesMiddleware(response, ctx.namespace);
|
|
1043
1080
|
}
|
|
1044
1081
|
return response;
|
|
@@ -1071,17 +1108,10 @@ function parseUserCookie2(value) {
|
|
|
1071
1108
|
return null;
|
|
1072
1109
|
}
|
|
1073
1110
|
}
|
|
1074
|
-
function decodeCookieValue(value) {
|
|
1075
|
-
try {
|
|
1076
|
-
return decodeURIComponent(value);
|
|
1077
|
-
} catch {
|
|
1078
|
-
return value;
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
1111
|
function readFirstCookieValue(request, names) {
|
|
1082
1112
|
for (const name of names) {
|
|
1083
|
-
const value = request
|
|
1084
|
-
if (value) return
|
|
1113
|
+
const value = readRequestCookieValue(request, name);
|
|
1114
|
+
if (value) return value;
|
|
1085
1115
|
}
|
|
1086
1116
|
return null;
|
|
1087
1117
|
}
|
|
@@ -1214,7 +1244,7 @@ async function handleSwitchOrg(request, ctx) {
|
|
|
1214
1244
|
if (request.method !== "POST") {
|
|
1215
1245
|
return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
|
|
1216
1246
|
}
|
|
1217
|
-
const currentSessionToken = request
|
|
1247
|
+
const currentSessionToken = readRequestCookieValue(request, ctx.cookieNames.SESSION);
|
|
1218
1248
|
let sessionToken = currentSessionToken && !isTokenExpired(currentSessionToken) ? currentSessionToken : null;
|
|
1219
1249
|
let restoredBaseSession = null;
|
|
1220
1250
|
if (!sessionToken) {
|
|
@@ -1226,7 +1256,7 @@ async function handleSwitchOrg(request, ctx) {
|
|
|
1226
1256
|
{ error: "Not authenticated", accessToken: null },
|
|
1227
1257
|
{ status: 401 }
|
|
1228
1258
|
);
|
|
1229
|
-
if (currentSessionToken || request
|
|
1259
|
+
if (currentSessionToken || hasRequestCookie(request, ctx.cookieNames.REFRESH)) {
|
|
1230
1260
|
clearAuthCookiesMiddleware(response, ctx.namespace);
|
|
1231
1261
|
}
|
|
1232
1262
|
return response;
|
|
@@ -1281,7 +1311,7 @@ async function handleSwitchOrg(request, ctx) {
|
|
|
1281
1311
|
...SECURE_COOKIE_OPTIONS,
|
|
1282
1312
|
maxAge: expiresIn
|
|
1283
1313
|
});
|
|
1284
|
-
const existingUser = parseUserCookie2(request
|
|
1314
|
+
const existingUser = parseUserCookie2(readRequestCookieValue(request, ctx.cookieNames.USER));
|
|
1285
1315
|
const user = data.user ?? existingUser?.user;
|
|
1286
1316
|
if (user) {
|
|
1287
1317
|
response.cookies.set(
|
|
@@ -1506,8 +1536,8 @@ function createSylphxMiddleware(userConfig = {}) {
|
|
|
1506
1536
|
if (pathname === `${config.authPrefix}/switch-org`) {
|
|
1507
1537
|
return handleSwitchOrg(request, ctx);
|
|
1508
1538
|
}
|
|
1509
|
-
const sessionToken = request
|
|
1510
|
-
const refreshToken = request
|
|
1539
|
+
const sessionToken = readRequestCookieValue(request, cookieNames.SESSION);
|
|
1540
|
+
const refreshToken = readRequestCookieValue(request, cookieNames.REFRESH);
|
|
1511
1541
|
const hasValidSession = sessionToken && !isTokenExpired(sessionToken);
|
|
1512
1542
|
const response = NextResponse.next({ request: { headers: request.headers } });
|
|
1513
1543
|
let isAuthenticated = hasValidSession;
|
|
@@ -3028,6 +3058,7 @@ export {
|
|
|
3028
3058
|
createSylphxMiddleware,
|
|
3029
3059
|
currentUser,
|
|
3030
3060
|
currentUserId,
|
|
3061
|
+
decodeCookieValue,
|
|
3031
3062
|
decodeUserId,
|
|
3032
3063
|
encodeUserId,
|
|
3033
3064
|
getAuthCookies,
|
|
@@ -3039,6 +3070,7 @@ export {
|
|
|
3039
3070
|
hasRefreshToken,
|
|
3040
3071
|
isSessionExpired,
|
|
3041
3072
|
parseUserCookie,
|
|
3073
|
+
readCookieValueFromHeader,
|
|
3042
3074
|
setAuthCookies,
|
|
3043
3075
|
setAuthCookiesMiddleware,
|
|
3044
3076
|
signOut,
|