@digilogiclabs/platform-core 1.10.0 → 1.11.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.
package/dist/auth.mjs CHANGED
@@ -1114,10 +1114,10 @@ function isValidBearerToken(request, secret) {
1114
1114
  function verifyCronAuth(request, secret) {
1115
1115
  const authHeader = request.headers.get("authorization");
1116
1116
  if (!authHeader?.startsWith("Bearer ")) {
1117
- return new Response(
1118
- JSON.stringify({ error: "Missing authorization" }),
1119
- { status: 401, headers: { "Content-Type": "application/json" } }
1120
- );
1117
+ return new Response(JSON.stringify({ error: "Missing authorization" }), {
1118
+ status: 401,
1119
+ headers: { "Content-Type": "application/json" }
1120
+ });
1121
1121
  }
1122
1122
  const token = authHeader.slice(7);
1123
1123
  const expectedSecret = secret ?? process.env.CRON_SECRET;
@@ -1129,10 +1129,10 @@ function verifyCronAuth(request, secret) {
1129
1129
  );
1130
1130
  }
1131
1131
  if (!constantTimeEqual(token, expectedSecret)) {
1132
- return new Response(
1133
- JSON.stringify({ error: "Invalid authorization" }),
1134
- { status: 401, headers: { "Content-Type": "application/json" } }
1135
- );
1132
+ return new Response(JSON.stringify({ error: "Invalid authorization" }), {
1133
+ status: 401,
1134
+ headers: { "Content-Type": "application/json" }
1135
+ });
1136
1136
  }
1137
1137
  return null;
1138
1138
  }
@@ -1284,6 +1284,172 @@ function clearStoredBetaCode(config = {}) {
1284
1284
  }
1285
1285
  }
1286
1286
 
1287
+ // src/auth/federated-logout.ts
1288
+ function expireCookie(name, options) {
1289
+ const parts = [
1290
+ `${name}=`,
1291
+ "Path=/",
1292
+ "Expires=Thu, 01 Jan 1970 00:00:00 GMT",
1293
+ "Max-Age=0",
1294
+ "SameSite=Lax"
1295
+ ];
1296
+ if (options?.hostPrefix) {
1297
+ parts.push("Secure");
1298
+ } else {
1299
+ parts.push("HttpOnly");
1300
+ if (options?.domain) parts.push(`Domain=${options.domain}`);
1301
+ if (options?.secure) parts.push("Secure");
1302
+ }
1303
+ return parts.join("; ");
1304
+ }
1305
+ function isAllowedCallbackUrl(url, baseUrl) {
1306
+ if (url.startsWith("/") && !url.startsWith("//")) return true;
1307
+ try {
1308
+ const parsed = new URL(url);
1309
+ const base = new URL(baseUrl);
1310
+ const allowedHosts = [base.hostname, `www.${base.hostname}`];
1311
+ if (base.hostname.startsWith("www.")) {
1312
+ allowedHosts.push(base.hostname.slice(4));
1313
+ }
1314
+ return allowedHosts.includes(parsed.hostname);
1315
+ } catch {
1316
+ return false;
1317
+ }
1318
+ }
1319
+ var AUTH_COOKIE_NAMES = [
1320
+ "authjs.session-token",
1321
+ "__Secure-authjs.session-token",
1322
+ "authjs.callback-url",
1323
+ "__Secure-authjs.callback-url",
1324
+ "authjs.csrf-token",
1325
+ "__Secure-authjs.csrf-token",
1326
+ "authjs.pkce.code_verifier",
1327
+ "__Secure-authjs.pkce.code_verifier",
1328
+ "authjs.state",
1329
+ "__Secure-authjs.state",
1330
+ // Legacy next-auth names
1331
+ "next-auth.session-token",
1332
+ "__Secure-next-auth.session-token",
1333
+ "next-auth.callback-url",
1334
+ "__Secure-next-auth.callback-url",
1335
+ "next-auth.csrf-token",
1336
+ "__Secure-next-auth.csrf-token"
1337
+ ];
1338
+ var HOST_COOKIES = [
1339
+ "__Host-authjs.csrf-token",
1340
+ "__Host-next-auth.csrf-token"
1341
+ ];
1342
+ function buildFederatedLogoutHandler(config) {
1343
+ const { auth, domain, baseUrlFallback, extraCookies = [], onError } = config;
1344
+ return async function GET(request) {
1345
+ const session = await auth();
1346
+ const url = new URL(request.url);
1347
+ const rawCallbackUrl = url.searchParams.get("callbackUrl") || "/";
1348
+ const queryIdToken = url.searchParams.get("id_token_hint");
1349
+ const baseUrl = process.env.NEXTAUTH_URL || process.env.AUTH_URL || baseUrlFallback;
1350
+ const callbackUrl = isAllowedCallbackUrl(rawCallbackUrl, baseUrl) ? rawCallbackUrl : "/";
1351
+ const postLogoutRedirectUri = callbackUrl.startsWith("http") ? callbackUrl : `${baseUrl}${callbackUrl}`;
1352
+ const keycloakIssuer = process.env.AUTH_KEYCLOAK_ISSUER;
1353
+ if (!keycloakIssuer) {
1354
+ onError?.("Missing AUTH_KEYCLOAK_ISSUER");
1355
+ return Response.redirect(
1356
+ new URL(
1357
+ "/api/auth/signout?callbackUrl=" + encodeURIComponent(callbackUrl),
1358
+ request.url
1359
+ ).toString()
1360
+ );
1361
+ }
1362
+ const refreshToken = session?.refreshToken;
1363
+ if (refreshToken) {
1364
+ try {
1365
+ const revokeUrl = `${keycloakIssuer}/protocol/openid-connect/revoke`;
1366
+ const clientId2 = process.env.AUTH_KEYCLOAK_ID;
1367
+ const clientSecret = process.env.AUTH_KEYCLOAK_SECRET;
1368
+ await fetch(revokeUrl, {
1369
+ method: "POST",
1370
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1371
+ body: new URLSearchParams({
1372
+ token: refreshToken,
1373
+ token_type_hint: "refresh_token",
1374
+ ...clientId2 && { client_id: clientId2 },
1375
+ ...clientSecret && { client_secret: clientSecret }
1376
+ })
1377
+ });
1378
+ } catch (err) {
1379
+ onError?.("Token revocation failed", err);
1380
+ }
1381
+ }
1382
+ const keycloakLogoutUrl = new URL(
1383
+ `${keycloakIssuer}/protocol/openid-connect/logout`
1384
+ );
1385
+ keycloakLogoutUrl.searchParams.set(
1386
+ "post_logout_redirect_uri",
1387
+ postLogoutRedirectUri
1388
+ );
1389
+ const clientId = process.env.AUTH_KEYCLOAK_ID;
1390
+ if (clientId) keycloakLogoutUrl.searchParams.set("client_id", clientId);
1391
+ const idToken = session?.idToken || queryIdToken;
1392
+ if (idToken) keycloakLogoutUrl.searchParams.set("id_token_hint", idToken);
1393
+ const escapedUrl = keycloakLogoutUrl.toString().replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1394
+ const html = `<!DOCTYPE html>
1395
+ <html><head>
1396
+ <meta charset="utf-8">
1397
+ <meta http-equiv="refresh" content="0;url=${escapedUrl}">
1398
+ <title>Signing out...</title>
1399
+ <style>body{display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;font-family:system-ui,sans-serif;background:#0a0a0a;color:#fff}p{font-size:1.1rem;opacity:0.7}</style>
1400
+ </head><body>
1401
+ <p>Signing out&hellip;</p>
1402
+ <script>window.location.replace(${JSON.stringify(keycloakLogoutUrl.toString()).replace(/</g, "\\u003c")});</script>
1403
+ </body></html>`;
1404
+ const response = new Response(html, {
1405
+ status: 200,
1406
+ headers: {
1407
+ "Content-Type": "text/html; charset=utf-8",
1408
+ "Cache-Control": "no-store, no-cache, must-revalidate",
1409
+ Pragma: "no-cache"
1410
+ }
1411
+ });
1412
+ const isProduction = process.env.NODE_ENV === "production";
1413
+ const isSecure = isProduction;
1414
+ const allCookieNames = [...AUTH_COOKIE_NAMES, ...extraCookies];
1415
+ for (const name of allCookieNames) {
1416
+ const needsSecure = isSecure || name.startsWith("__Secure-");
1417
+ response.headers.append(
1418
+ "Set-Cookie",
1419
+ expireCookie(name, { secure: needsSecure })
1420
+ );
1421
+ if (isProduction) {
1422
+ response.headers.append(
1423
+ "Set-Cookie",
1424
+ expireCookie(name, { domain, secure: needsSecure })
1425
+ );
1426
+ }
1427
+ }
1428
+ for (const name of HOST_COOKIES) {
1429
+ response.headers.append(
1430
+ "Set-Cookie",
1431
+ expireCookie(name, { hostPrefix: true })
1432
+ );
1433
+ }
1434
+ return response;
1435
+ };
1436
+ }
1437
+
1438
+ // src/auth/lazy-rate-limit-store.ts
1439
+ function createLazyRateLimitStore(getRedis, options = {}) {
1440
+ const { keyPrefix = "rl:" } = options;
1441
+ let store;
1442
+ let initialized = false;
1443
+ return function getRateLimitStore() {
1444
+ if (initialized) return store;
1445
+ initialized = true;
1446
+ const redis = getRedis();
1447
+ if (!redis) return void 0;
1448
+ store = createRedisRateLimitStore(redis, { keyPrefix });
1449
+ return store;
1450
+ };
1451
+ }
1452
+
1287
1453
  // src/env.ts
1288
1454
  function getRequiredEnv(key) {
1289
1455
  const value = process.env[key];
@@ -1397,6 +1563,7 @@ export {
1397
1563
  buildAllowlist,
1398
1564
  buildAuthCookies,
1399
1565
  buildErrorBody,
1566
+ buildFederatedLogoutHandler,
1400
1567
  buildKeycloakCallbacks,
1401
1568
  buildPagination,
1402
1569
  buildRateLimitHeaders,
@@ -1414,6 +1581,7 @@ export {
1414
1581
  createAuditLogger,
1415
1582
  createBetaClient,
1416
1583
  createFeatureFlags,
1584
+ createLazyRateLimitStore,
1417
1585
  createMemoryRateLimitStore,
1418
1586
  createRedisRateLimitStore,
1419
1587
  createSafeTextSchema,