@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.d.mts +103 -1
- package/dist/auth.d.ts +103 -1
- package/dist/auth.js +178 -8
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +176 -8
- package/dist/auth.mjs.map +1 -1
- package/dist/migrate.js +0 -0
- package/package.json +11 -11
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
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
1134
|
-
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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…</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,
|