@camstack/addon-auth 1.2.144 → 1.2.146
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/{dist-ChwITXNa.mjs → dist-BtJOajm3.mjs} +475 -12
- package/dist/{dist-0YTEFDYk.js → dist-DtqB-MTf.js} +498 -11
- package/dist/oidc/auth-oidc.addon.js +123 -15
- package/dist/oidc/auth-oidc.addon.mjs +123 -15
- package/dist/webauthn/_stub.js +1 -1
- package/dist/webauthn/{_virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-ZRpCO_gp.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-C3_j2tlC.mjs} +2 -2
- package/dist/webauthn/_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C_j9tv24.mjs +26 -0
- package/dist/webauthn/auth-webauthn.addon.js +16 -4
- package/dist/webauthn/auth-webauthn.addon.mjs +16 -5
- package/dist/webauthn/{hostInit-YhJ9npQM.mjs → hostInit-H35G8h3o.mjs} +2 -2
- package/dist/webauthn/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/webauthn/_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-xhY74yma.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-DtqB-MTf.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
node_crypto = require_dist.__toESM(node_crypto);
|
|
8
8
|
//#region ../../node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
@@ -938,6 +938,28 @@ var STATE_TTL_MS = 5 * 6e4;
|
|
|
938
938
|
var LINK_INTENT_PATTERN = /^[A-Za-z0-9_-]{16,128}$/;
|
|
939
939
|
/** Bound on in-flight logins: `/start` is public, so a flood must not grow the map. */
|
|
940
940
|
var MAX_PENDING_STATES = 1e3;
|
|
941
|
+
/** In-flight logins one client may hold (D858): one browser flooding `/start` cannot fill the map for everyone. */
|
|
942
|
+
var MAX_PENDING_PER_CLIENT = 20;
|
|
943
|
+
/**
|
|
944
|
+
* A step-up must follow a login that happened no earlier than this before the
|
|
945
|
+
* flow began (D858). The authorize request asks for `prompt=login&max_age=0`;
|
|
946
|
+
* this is the check that the IdP actually honoured it.
|
|
947
|
+
*/
|
|
948
|
+
var STEP_UP_MAX_AUTH_AGE_MS = 6e4;
|
|
949
|
+
/** The pending map is full, or this client already holds its share. Live states are never evicted for it. */
|
|
950
|
+
var PendingStatesFullError = class extends Error {};
|
|
951
|
+
/** A step-up whose id_token proves no login just before the flow began. */
|
|
952
|
+
var StepUpNotFreshError = class extends Error {};
|
|
953
|
+
/**
|
|
954
|
+
* Who a `/start` is counted against. The hub already limits each real client
|
|
955
|
+
* address per minute before a route runs; the addon sees only headers, so this
|
|
956
|
+
* is a best-effort key (a spoofed header only rotates the bucket, and the
|
|
957
|
+
* hub's limit and the global cap still bound the total).
|
|
958
|
+
*/
|
|
959
|
+
function clientKeyOf(headers) {
|
|
960
|
+
const forwarded = headers?.["x-forwarded-for"]?.split(",")[0]?.trim();
|
|
961
|
+
return headers?.["cf-connecting-ip"] ?? headers?.["x-real-ip"] ?? forwarded ?? "unknown";
|
|
962
|
+
}
|
|
941
963
|
/**
|
|
942
964
|
* Origin + mount prefix of a pinned `redirect_uri`: the part before the
|
|
943
965
|
* callback path when it ends with it, else just its origin.
|
|
@@ -1108,7 +1130,11 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1108
1130
|
icon: require_dist.resolveIconKey(inst.config.icon, DEFAULT_PROVIDER.icon),
|
|
1109
1131
|
startUrl: `/addon/auth-oidc/${id}/start`,
|
|
1110
1132
|
stage: "primary",
|
|
1111
|
-
linkable: true
|
|
1133
|
+
linkable: true,
|
|
1134
|
+
app: {
|
|
1135
|
+
kind: "browser",
|
|
1136
|
+
startPath: `/addon/auth-oidc/${id}/start`
|
|
1137
|
+
}
|
|
1112
1138
|
});
|
|
1113
1139
|
}
|
|
1114
1140
|
return out;
|
|
@@ -1244,12 +1270,10 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1244
1270
|
const nonce = node_crypto.randomBytes(16).toString("base64url");
|
|
1245
1271
|
const { redirectUri, returnBase } = await this.resolveRedirect(providerId, options.headers ?? null);
|
|
1246
1272
|
this.pruneExpiredStates();
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
if (oldest.done) break;
|
|
1250
|
-
this.pendingStates.delete(oldest.value);
|
|
1251
|
-
}
|
|
1273
|
+
const clientKey = clientKeyOf(options.headers);
|
|
1274
|
+
if (this.pendingStates.size >= MAX_PENDING_STATES || [...this.pendingStates.values()].filter((p) => p.clientKey === clientKey).length >= MAX_PENDING_PER_CLIENT) throw new PendingStatesFullError("Too many sign-ins in progress - try again shortly");
|
|
1252
1275
|
this.pendingStates.set(state, {
|
|
1276
|
+
clientKey,
|
|
1253
1277
|
codeVerifier,
|
|
1254
1278
|
nonce,
|
|
1255
1279
|
providerId,
|
|
@@ -1258,7 +1282,8 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1258
1282
|
popup: options.popup === true,
|
|
1259
1283
|
createdAt: Date.now(),
|
|
1260
1284
|
...linkIntent !== void 0 ? { linkIntent } : {},
|
|
1261
|
-
...options.browserBinding !== void 0 ? { browserBinding: options.browserBinding } : {}
|
|
1285
|
+
...options.browserBinding !== void 0 ? { browserBinding: options.browserBinding } : {},
|
|
1286
|
+
...options.app !== void 0 ? { app: options.app } : {}
|
|
1262
1287
|
});
|
|
1263
1288
|
const params = new URLSearchParams({
|
|
1264
1289
|
response_type: "code",
|
|
@@ -1270,17 +1295,27 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1270
1295
|
code_challenge: codeChallenge,
|
|
1271
1296
|
code_challenge_method: "S256"
|
|
1272
1297
|
});
|
|
1298
|
+
if (options.app?.purpose === "step-up") {
|
|
1299
|
+
params.set("prompt", "login");
|
|
1300
|
+
params.set("max_age", "0");
|
|
1301
|
+
}
|
|
1273
1302
|
return {
|
|
1274
1303
|
url: `${doc.authorization_endpoint}?${params.toString()}`,
|
|
1275
1304
|
returnBase
|
|
1276
1305
|
};
|
|
1277
1306
|
}
|
|
1307
|
+
/** The pending state for `state`, unless it is gone or older than its TTL. */
|
|
1308
|
+
livePending(state) {
|
|
1309
|
+
const pending = this.pendingStates.get(state);
|
|
1310
|
+
if (pending === void 0) return void 0;
|
|
1311
|
+
return Date.now() - pending.createdAt > STATE_TTL_MS ? void 0 : pending;
|
|
1312
|
+
}
|
|
1278
1313
|
pruneExpiredStates() {
|
|
1279
1314
|
const now = Date.now();
|
|
1280
1315
|
for (const [k, v] of this.pendingStates.entries()) if (now - v.createdAt > STATE_TTL_MS) this.pendingStates.delete(k);
|
|
1281
1316
|
}
|
|
1282
1317
|
async handleCallback(code, state) {
|
|
1283
|
-
const pending = this.
|
|
1318
|
+
const pending = this.livePending(state);
|
|
1284
1319
|
if (!pending) throw new Error("Unknown or expired OIDC state — login session timed out, please retry");
|
|
1285
1320
|
this.pendingStates.delete(state);
|
|
1286
1321
|
const providerId = pending.providerId;
|
|
@@ -1321,6 +1356,10 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1321
1356
|
throw new Error(`OIDC id_token verification failed: ${require_dist.errMsg(err)}`, { cause: err });
|
|
1322
1357
|
}
|
|
1323
1358
|
if (typeof payload["nonce"] !== "string" || payload["nonce"] !== pending.nonce) throw new Error("OIDC id_token nonce mismatch — possible replay attack");
|
|
1359
|
+
if (pending.app?.purpose === "step-up") {
|
|
1360
|
+
const authTime = payload["auth_time"];
|
|
1361
|
+
if (typeof authTime !== "number" || !Number.isFinite(authTime) || authTime * 1e3 < pending.createdAt - STEP_UP_MAX_AUTH_AGE_MS) throw new StepUpNotFreshError("OIDC step-up id_token proves no fresh login");
|
|
1362
|
+
}
|
|
1324
1363
|
const sub = typeof payload.sub === "string" ? payload.sub : null;
|
|
1325
1364
|
if (!sub) throw new Error("OIDC callback: id_token missing required `sub` claim");
|
|
1326
1365
|
const emailClaim = typeof payload["email"] === "string" ? payload["email"] : void 0;
|
|
@@ -1339,7 +1378,8 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1339
1378
|
providerId,
|
|
1340
1379
|
returnBase: pending.returnBase,
|
|
1341
1380
|
popup: pending.popup,
|
|
1342
|
-
...pending.browserBinding !== void 0 ? { browserBinding: pending.browserBinding } : {}
|
|
1381
|
+
...pending.browserBinding !== void 0 ? { browserBinding: pending.browserBinding } : {},
|
|
1382
|
+
...pending.app !== void 0 ? { app: pending.app } : {}
|
|
1343
1383
|
};
|
|
1344
1384
|
}
|
|
1345
1385
|
validateLocalSessionToken(_token) {
|
|
@@ -1358,21 +1398,53 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1358
1398
|
reply.send({ error: "Malformed link request" });
|
|
1359
1399
|
return;
|
|
1360
1400
|
}
|
|
1361
|
-
const
|
|
1362
|
-
const
|
|
1401
|
+
const query = req.query;
|
|
1402
|
+
const popupParam = query["popup"];
|
|
1403
|
+
let app;
|
|
1404
|
+
if (query["app"] === "1") {
|
|
1405
|
+
const parsed = require_dist.parseAppFlowRequest(query);
|
|
1406
|
+
if (linkParam !== void 0 || popupParam === "1" || !parsed.ok) {
|
|
1407
|
+
this.ctx.logger.warn("OIDC app sign-in refused at start", { meta: {
|
|
1408
|
+
provider: providerId,
|
|
1409
|
+
reason: parsed.ok ? "combined-flow" : parsed.reason
|
|
1410
|
+
} });
|
|
1411
|
+
const reason = parsed.ok ? "combined-flow" : parsed.reason;
|
|
1412
|
+
reply.code(303);
|
|
1413
|
+
reply.header("Cache-Control", "no-store");
|
|
1414
|
+
reply.header("Location", `${require_dist.APP_REFUSED_PATH}?reason=${reason}`);
|
|
1415
|
+
reply.send("");
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
app = parsed.request;
|
|
1419
|
+
}
|
|
1420
|
+
const state = `${app !== void 0 ? "a." : ""}${node_crypto.randomBytes(16).toString("base64url")}`;
|
|
1363
1421
|
const browserState = node_crypto.randomBytes(32).toString("base64url");
|
|
1364
1422
|
try {
|
|
1365
1423
|
const { url, returnBase } = await this.prepareLogin(providerId, state, {
|
|
1366
1424
|
...linkParam !== void 0 ? { linkIntent: linkParam } : {},
|
|
1367
1425
|
headers: req.headers,
|
|
1368
1426
|
popup: popupParam === "1",
|
|
1369
|
-
browserBinding: node_crypto.createHash("sha256").update(browserState).digest("hex")
|
|
1427
|
+
browserBinding: node_crypto.createHash("sha256").update(browserState).digest("hex"),
|
|
1428
|
+
...app !== void 0 ? { app } : {}
|
|
1370
1429
|
});
|
|
1371
1430
|
reply.code(302);
|
|
1372
1431
|
reply.header("Set-Cookie", ssoStateCookie(browserState, returnBase.startsWith("https:")));
|
|
1373
1432
|
reply.header("Location", url);
|
|
1374
1433
|
reply.send("");
|
|
1375
1434
|
} catch (err) {
|
|
1435
|
+
if (err instanceof PendingStatesFullError) {
|
|
1436
|
+
this.ctx.logger.warn("OIDC sign-in refused: too many in progress", { meta: { provider: providerId } });
|
|
1437
|
+
if (app !== void 0) {
|
|
1438
|
+
reply.code(302);
|
|
1439
|
+
reply.header("Location", require_dist.appFlowLocation(app.redirect, { error: "too-many-requests" }, app.state));
|
|
1440
|
+
reply.send("");
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
reply.code(429);
|
|
1444
|
+
reply.header("Retry-After", "30");
|
|
1445
|
+
reply.send({ error: "Too many sign-ins in progress - try again shortly" });
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1376
1448
|
reply.code(500);
|
|
1377
1449
|
reply.send({
|
|
1378
1450
|
error: "OIDC start failed",
|
|
@@ -1391,6 +1463,27 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1391
1463
|
const code = query["code"];
|
|
1392
1464
|
const state = query["state"];
|
|
1393
1465
|
const errorParam = query["error"];
|
|
1466
|
+
const pendingApp = state !== void 0 ? this.livePending(state)?.app : void 0;
|
|
1467
|
+
const appFlowState = state?.startsWith("a.") === true;
|
|
1468
|
+
const answerApp = (error) => {
|
|
1469
|
+
this.ctx.logger.warn("OIDC app sign-in refused at callback", { meta: {
|
|
1470
|
+
provider: providerId,
|
|
1471
|
+
error
|
|
1472
|
+
} });
|
|
1473
|
+
if (state !== void 0) this.pendingStates.delete(state);
|
|
1474
|
+
reply.code(302);
|
|
1475
|
+
reply.header("Cache-Control", "no-store");
|
|
1476
|
+
reply.header("Location", pendingApp !== void 0 ? require_dist.appFlowLocation(pendingApp.redirect, { error }, pendingApp.state) : require_dist.appFlowLocation(require_dist.APP_FLOW_DEFAULT_REDIRECT, { error }));
|
|
1477
|
+
reply.send("");
|
|
1478
|
+
};
|
|
1479
|
+
if (appFlowState && pendingApp === void 0) {
|
|
1480
|
+
answerApp(errorParam ? "idp-error" : "state-expired");
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
if (errorParam && pendingApp !== void 0) {
|
|
1484
|
+
answerApp("idp-error");
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1394
1487
|
if (errorParam) {
|
|
1395
1488
|
reply.code(400);
|
|
1396
1489
|
reply.send({
|
|
@@ -1408,6 +1501,10 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1408
1501
|
try {
|
|
1409
1502
|
const result = await this.handleCallback(code, state);
|
|
1410
1503
|
if (result.providerId !== providerId) {
|
|
1504
|
+
if (result.app !== void 0) {
|
|
1505
|
+
answerApp("provider-mismatch");
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1411
1508
|
reply.code(400);
|
|
1412
1509
|
reply.send({ error: "OIDC callback does not match the provider the login started at" });
|
|
1413
1510
|
return;
|
|
@@ -1422,14 +1519,25 @@ var AuthOidcAddon = class extends require_dist.BaseAddon {
|
|
|
1422
1519
|
...result.linkIntent !== void 0 ? { linkIntent: result.linkIntent } : {},
|
|
1423
1520
|
issuer: result.issuer,
|
|
1424
1521
|
jti: node_crypto.randomBytes(16).toString("base64url"),
|
|
1425
|
-
...result.browserBinding !== void 0 ? { browserBinding: result.browserBinding } : {}
|
|
1522
|
+
...result.browserBinding !== void 0 ? { browserBinding: result.browserBinding } : {},
|
|
1523
|
+
...result.app !== void 0 ? { app: {
|
|
1524
|
+
codeChallenge: result.app.codeChallenge,
|
|
1525
|
+
state: result.app.state,
|
|
1526
|
+
purpose: result.app.purpose,
|
|
1527
|
+
redirect: result.app.redirect
|
|
1528
|
+
} } : {}
|
|
1426
1529
|
} });
|
|
1427
1530
|
const finish = new URLSearchParams({ bridge: bridgeToken });
|
|
1428
1531
|
if (result.popup) finish.set("popup", "1");
|
|
1532
|
+
const finishPath = result.app !== void 0 ? "finish-app" : "finish";
|
|
1429
1533
|
reply.code(302);
|
|
1430
|
-
reply.header("Location", `${result.returnBase}/api/auth/sso
|
|
1534
|
+
reply.header("Location", `${result.returnBase}/api/auth/sso/${finishPath}?${finish.toString()}`);
|
|
1431
1535
|
reply.send("");
|
|
1432
1536
|
} catch (err) {
|
|
1537
|
+
if (pendingApp !== void 0) {
|
|
1538
|
+
answerApp(err instanceof StepUpNotFreshError ? "step-up-not-fresh" : "idp-failed");
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1433
1541
|
reply.code(500);
|
|
1434
1542
|
reply.send({
|
|
1435
1543
|
error: "OIDC callback failed",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as globalSecretScope, E as toSecretFieldView, S as arrayItemFieldPath, T as isSecretMarker, _ as resolveIconKey, b as BaseAddon, c as authProviderCapability, d as loginMethodCapability, f as normalizeOrigin, g as requestMountPrefix, h as pickRequestOrigin, i as addonRoutesCapability, l as buildAddonRouteProvider, m as parseAppFlowRequest, n as APP_REFUSED_PATH, o as advertisedHubOrigins, p as oidcConnectionsCapability, r as SSO_STATE_COOKIE, s as appFlowLocation, t as APP_FLOW_DEFAULT_REDIRECT, u as claimedRequestOrigins, w as isSecretClear, x as SECRET_CLEAR, y as errMsg } from "../dist-BtJOajm3.mjs";
|
|
2
2
|
import * as crypto$1 from "node:crypto";
|
|
3
3
|
//#region ../../node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
4
4
|
var encoder = new TextEncoder(), decoder = new TextDecoder(), strictDecoder = new TextDecoder("utf-8", { fatal: !0 });
|
|
@@ -933,6 +933,28 @@ var STATE_TTL_MS = 5 * 6e4;
|
|
|
933
933
|
var LINK_INTENT_PATTERN = /^[A-Za-z0-9_-]{16,128}$/;
|
|
934
934
|
/** Bound on in-flight logins: `/start` is public, so a flood must not grow the map. */
|
|
935
935
|
var MAX_PENDING_STATES = 1e3;
|
|
936
|
+
/** In-flight logins one client may hold (D858): one browser flooding `/start` cannot fill the map for everyone. */
|
|
937
|
+
var MAX_PENDING_PER_CLIENT = 20;
|
|
938
|
+
/**
|
|
939
|
+
* A step-up must follow a login that happened no earlier than this before the
|
|
940
|
+
* flow began (D858). The authorize request asks for `prompt=login&max_age=0`;
|
|
941
|
+
* this is the check that the IdP actually honoured it.
|
|
942
|
+
*/
|
|
943
|
+
var STEP_UP_MAX_AUTH_AGE_MS = 6e4;
|
|
944
|
+
/** The pending map is full, or this client already holds its share. Live states are never evicted for it. */
|
|
945
|
+
var PendingStatesFullError = class extends Error {};
|
|
946
|
+
/** A step-up whose id_token proves no login just before the flow began. */
|
|
947
|
+
var StepUpNotFreshError = class extends Error {};
|
|
948
|
+
/**
|
|
949
|
+
* Who a `/start` is counted against. The hub already limits each real client
|
|
950
|
+
* address per minute before a route runs; the addon sees only headers, so this
|
|
951
|
+
* is a best-effort key (a spoofed header only rotates the bucket, and the
|
|
952
|
+
* hub's limit and the global cap still bound the total).
|
|
953
|
+
*/
|
|
954
|
+
function clientKeyOf(headers) {
|
|
955
|
+
const forwarded = headers?.["x-forwarded-for"]?.split(",")[0]?.trim();
|
|
956
|
+
return headers?.["cf-connecting-ip"] ?? headers?.["x-real-ip"] ?? forwarded ?? "unknown";
|
|
957
|
+
}
|
|
936
958
|
/**
|
|
937
959
|
* Origin + mount prefix of a pinned `redirect_uri`: the part before the
|
|
938
960
|
* callback path when it ends with it, else just its origin.
|
|
@@ -1103,7 +1125,11 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1103
1125
|
icon: resolveIconKey(inst.config.icon, DEFAULT_PROVIDER.icon),
|
|
1104
1126
|
startUrl: `/addon/auth-oidc/${id}/start`,
|
|
1105
1127
|
stage: "primary",
|
|
1106
|
-
linkable: true
|
|
1128
|
+
linkable: true,
|
|
1129
|
+
app: {
|
|
1130
|
+
kind: "browser",
|
|
1131
|
+
startPath: `/addon/auth-oidc/${id}/start`
|
|
1132
|
+
}
|
|
1107
1133
|
});
|
|
1108
1134
|
}
|
|
1109
1135
|
return out;
|
|
@@ -1239,12 +1265,10 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1239
1265
|
const nonce = crypto$1.randomBytes(16).toString("base64url");
|
|
1240
1266
|
const { redirectUri, returnBase } = await this.resolveRedirect(providerId, options.headers ?? null);
|
|
1241
1267
|
this.pruneExpiredStates();
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if (oldest.done) break;
|
|
1245
|
-
this.pendingStates.delete(oldest.value);
|
|
1246
|
-
}
|
|
1268
|
+
const clientKey = clientKeyOf(options.headers);
|
|
1269
|
+
if (this.pendingStates.size >= MAX_PENDING_STATES || [...this.pendingStates.values()].filter((p) => p.clientKey === clientKey).length >= MAX_PENDING_PER_CLIENT) throw new PendingStatesFullError("Too many sign-ins in progress - try again shortly");
|
|
1247
1270
|
this.pendingStates.set(state, {
|
|
1271
|
+
clientKey,
|
|
1248
1272
|
codeVerifier,
|
|
1249
1273
|
nonce,
|
|
1250
1274
|
providerId,
|
|
@@ -1253,7 +1277,8 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1253
1277
|
popup: options.popup === true,
|
|
1254
1278
|
createdAt: Date.now(),
|
|
1255
1279
|
...linkIntent !== void 0 ? { linkIntent } : {},
|
|
1256
|
-
...options.browserBinding !== void 0 ? { browserBinding: options.browserBinding } : {}
|
|
1280
|
+
...options.browserBinding !== void 0 ? { browserBinding: options.browserBinding } : {},
|
|
1281
|
+
...options.app !== void 0 ? { app: options.app } : {}
|
|
1257
1282
|
});
|
|
1258
1283
|
const params = new URLSearchParams({
|
|
1259
1284
|
response_type: "code",
|
|
@@ -1265,17 +1290,27 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1265
1290
|
code_challenge: codeChallenge,
|
|
1266
1291
|
code_challenge_method: "S256"
|
|
1267
1292
|
});
|
|
1293
|
+
if (options.app?.purpose === "step-up") {
|
|
1294
|
+
params.set("prompt", "login");
|
|
1295
|
+
params.set("max_age", "0");
|
|
1296
|
+
}
|
|
1268
1297
|
return {
|
|
1269
1298
|
url: `${doc.authorization_endpoint}?${params.toString()}`,
|
|
1270
1299
|
returnBase
|
|
1271
1300
|
};
|
|
1272
1301
|
}
|
|
1302
|
+
/** The pending state for `state`, unless it is gone or older than its TTL. */
|
|
1303
|
+
livePending(state) {
|
|
1304
|
+
const pending = this.pendingStates.get(state);
|
|
1305
|
+
if (pending === void 0) return void 0;
|
|
1306
|
+
return Date.now() - pending.createdAt > STATE_TTL_MS ? void 0 : pending;
|
|
1307
|
+
}
|
|
1273
1308
|
pruneExpiredStates() {
|
|
1274
1309
|
const now = Date.now();
|
|
1275
1310
|
for (const [k, v] of this.pendingStates.entries()) if (now - v.createdAt > STATE_TTL_MS) this.pendingStates.delete(k);
|
|
1276
1311
|
}
|
|
1277
1312
|
async handleCallback(code, state) {
|
|
1278
|
-
const pending = this.
|
|
1313
|
+
const pending = this.livePending(state);
|
|
1279
1314
|
if (!pending) throw new Error("Unknown or expired OIDC state — login session timed out, please retry");
|
|
1280
1315
|
this.pendingStates.delete(state);
|
|
1281
1316
|
const providerId = pending.providerId;
|
|
@@ -1316,6 +1351,10 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1316
1351
|
throw new Error(`OIDC id_token verification failed: ${errMsg(err)}`, { cause: err });
|
|
1317
1352
|
}
|
|
1318
1353
|
if (typeof payload["nonce"] !== "string" || payload["nonce"] !== pending.nonce) throw new Error("OIDC id_token nonce mismatch — possible replay attack");
|
|
1354
|
+
if (pending.app?.purpose === "step-up") {
|
|
1355
|
+
const authTime = payload["auth_time"];
|
|
1356
|
+
if (typeof authTime !== "number" || !Number.isFinite(authTime) || authTime * 1e3 < pending.createdAt - STEP_UP_MAX_AUTH_AGE_MS) throw new StepUpNotFreshError("OIDC step-up id_token proves no fresh login");
|
|
1357
|
+
}
|
|
1319
1358
|
const sub = typeof payload.sub === "string" ? payload.sub : null;
|
|
1320
1359
|
if (!sub) throw new Error("OIDC callback: id_token missing required `sub` claim");
|
|
1321
1360
|
const emailClaim = typeof payload["email"] === "string" ? payload["email"] : void 0;
|
|
@@ -1334,7 +1373,8 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1334
1373
|
providerId,
|
|
1335
1374
|
returnBase: pending.returnBase,
|
|
1336
1375
|
popup: pending.popup,
|
|
1337
|
-
...pending.browserBinding !== void 0 ? { browserBinding: pending.browserBinding } : {}
|
|
1376
|
+
...pending.browserBinding !== void 0 ? { browserBinding: pending.browserBinding } : {},
|
|
1377
|
+
...pending.app !== void 0 ? { app: pending.app } : {}
|
|
1338
1378
|
};
|
|
1339
1379
|
}
|
|
1340
1380
|
validateLocalSessionToken(_token) {
|
|
@@ -1353,21 +1393,53 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1353
1393
|
reply.send({ error: "Malformed link request" });
|
|
1354
1394
|
return;
|
|
1355
1395
|
}
|
|
1356
|
-
const
|
|
1357
|
-
const
|
|
1396
|
+
const query = req.query;
|
|
1397
|
+
const popupParam = query["popup"];
|
|
1398
|
+
let app;
|
|
1399
|
+
if (query["app"] === "1") {
|
|
1400
|
+
const parsed = parseAppFlowRequest(query);
|
|
1401
|
+
if (linkParam !== void 0 || popupParam === "1" || !parsed.ok) {
|
|
1402
|
+
this.ctx.logger.warn("OIDC app sign-in refused at start", { meta: {
|
|
1403
|
+
provider: providerId,
|
|
1404
|
+
reason: parsed.ok ? "combined-flow" : parsed.reason
|
|
1405
|
+
} });
|
|
1406
|
+
const reason = parsed.ok ? "combined-flow" : parsed.reason;
|
|
1407
|
+
reply.code(303);
|
|
1408
|
+
reply.header("Cache-Control", "no-store");
|
|
1409
|
+
reply.header("Location", `${APP_REFUSED_PATH}?reason=${reason}`);
|
|
1410
|
+
reply.send("");
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
app = parsed.request;
|
|
1414
|
+
}
|
|
1415
|
+
const state = `${app !== void 0 ? "a." : ""}${crypto$1.randomBytes(16).toString("base64url")}`;
|
|
1358
1416
|
const browserState = crypto$1.randomBytes(32).toString("base64url");
|
|
1359
1417
|
try {
|
|
1360
1418
|
const { url, returnBase } = await this.prepareLogin(providerId, state, {
|
|
1361
1419
|
...linkParam !== void 0 ? { linkIntent: linkParam } : {},
|
|
1362
1420
|
headers: req.headers,
|
|
1363
1421
|
popup: popupParam === "1",
|
|
1364
|
-
browserBinding: crypto$1.createHash("sha256").update(browserState).digest("hex")
|
|
1422
|
+
browserBinding: crypto$1.createHash("sha256").update(browserState).digest("hex"),
|
|
1423
|
+
...app !== void 0 ? { app } : {}
|
|
1365
1424
|
});
|
|
1366
1425
|
reply.code(302);
|
|
1367
1426
|
reply.header("Set-Cookie", ssoStateCookie(browserState, returnBase.startsWith("https:")));
|
|
1368
1427
|
reply.header("Location", url);
|
|
1369
1428
|
reply.send("");
|
|
1370
1429
|
} catch (err) {
|
|
1430
|
+
if (err instanceof PendingStatesFullError) {
|
|
1431
|
+
this.ctx.logger.warn("OIDC sign-in refused: too many in progress", { meta: { provider: providerId } });
|
|
1432
|
+
if (app !== void 0) {
|
|
1433
|
+
reply.code(302);
|
|
1434
|
+
reply.header("Location", appFlowLocation(app.redirect, { error: "too-many-requests" }, app.state));
|
|
1435
|
+
reply.send("");
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
reply.code(429);
|
|
1439
|
+
reply.header("Retry-After", "30");
|
|
1440
|
+
reply.send({ error: "Too many sign-ins in progress - try again shortly" });
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1371
1443
|
reply.code(500);
|
|
1372
1444
|
reply.send({
|
|
1373
1445
|
error: "OIDC start failed",
|
|
@@ -1386,6 +1458,27 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1386
1458
|
const code = query["code"];
|
|
1387
1459
|
const state = query["state"];
|
|
1388
1460
|
const errorParam = query["error"];
|
|
1461
|
+
const pendingApp = state !== void 0 ? this.livePending(state)?.app : void 0;
|
|
1462
|
+
const appFlowState = state?.startsWith("a.") === true;
|
|
1463
|
+
const answerApp = (error) => {
|
|
1464
|
+
this.ctx.logger.warn("OIDC app sign-in refused at callback", { meta: {
|
|
1465
|
+
provider: providerId,
|
|
1466
|
+
error
|
|
1467
|
+
} });
|
|
1468
|
+
if (state !== void 0) this.pendingStates.delete(state);
|
|
1469
|
+
reply.code(302);
|
|
1470
|
+
reply.header("Cache-Control", "no-store");
|
|
1471
|
+
reply.header("Location", pendingApp !== void 0 ? appFlowLocation(pendingApp.redirect, { error }, pendingApp.state) : appFlowLocation(APP_FLOW_DEFAULT_REDIRECT, { error }));
|
|
1472
|
+
reply.send("");
|
|
1473
|
+
};
|
|
1474
|
+
if (appFlowState && pendingApp === void 0) {
|
|
1475
|
+
answerApp(errorParam ? "idp-error" : "state-expired");
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
if (errorParam && pendingApp !== void 0) {
|
|
1479
|
+
answerApp("idp-error");
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1389
1482
|
if (errorParam) {
|
|
1390
1483
|
reply.code(400);
|
|
1391
1484
|
reply.send({
|
|
@@ -1403,6 +1496,10 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1403
1496
|
try {
|
|
1404
1497
|
const result = await this.handleCallback(code, state);
|
|
1405
1498
|
if (result.providerId !== providerId) {
|
|
1499
|
+
if (result.app !== void 0) {
|
|
1500
|
+
answerApp("provider-mismatch");
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1406
1503
|
reply.code(400);
|
|
1407
1504
|
reply.send({ error: "OIDC callback does not match the provider the login started at" });
|
|
1408
1505
|
return;
|
|
@@ -1417,14 +1514,25 @@ var AuthOidcAddon = class extends BaseAddon {
|
|
|
1417
1514
|
...result.linkIntent !== void 0 ? { linkIntent: result.linkIntent } : {},
|
|
1418
1515
|
issuer: result.issuer,
|
|
1419
1516
|
jti: crypto$1.randomBytes(16).toString("base64url"),
|
|
1420
|
-
...result.browserBinding !== void 0 ? { browserBinding: result.browserBinding } : {}
|
|
1517
|
+
...result.browserBinding !== void 0 ? { browserBinding: result.browserBinding } : {},
|
|
1518
|
+
...result.app !== void 0 ? { app: {
|
|
1519
|
+
codeChallenge: result.app.codeChallenge,
|
|
1520
|
+
state: result.app.state,
|
|
1521
|
+
purpose: result.app.purpose,
|
|
1522
|
+
redirect: result.app.redirect
|
|
1523
|
+
} } : {}
|
|
1421
1524
|
} });
|
|
1422
1525
|
const finish = new URLSearchParams({ bridge: bridgeToken });
|
|
1423
1526
|
if (result.popup) finish.set("popup", "1");
|
|
1527
|
+
const finishPath = result.app !== void 0 ? "finish-app" : "finish";
|
|
1424
1528
|
reply.code(302);
|
|
1425
|
-
reply.header("Location", `${result.returnBase}/api/auth/sso
|
|
1529
|
+
reply.header("Location", `${result.returnBase}/api/auth/sso/${finishPath}?${finish.toString()}`);
|
|
1426
1530
|
reply.send("");
|
|
1427
1531
|
} catch (err) {
|
|
1532
|
+
if (pendingApp !== void 0) {
|
|
1533
|
+
answerApp(err instanceof StepUpNotFreshError ? "step-up-not-fresh" : "idp-failed");
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1428
1536
|
reply.code(500);
|
|
1429
1537
|
reply.send({
|
|
1430
1538
|
error: "OIDC callback failed",
|
package/dist/webauthn/_stub.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as e, r as t, t as n } from "./_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare__react__loadShare__.js-BIIa6vDX.mjs";
|
|
2
2
|
import { n as r, t as i } from "./_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-CQ-aEQ9b.mjs";
|
|
3
|
-
import { t as a } from "./_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-
|
|
3
|
+
import { t as a } from "./_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C_j9tv24.mjs";
|
|
4
4
|
import { n as o, r as s, t as c } from "./_virtual_mf___mfe_internal__addon_auth_webauthn_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-BL2etuqg.mjs";
|
|
5
5
|
//#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
|
|
6
6
|
var l = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), u = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), d = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), f = (e) => {
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.372",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_auth_webauthn_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.290",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_auth_webauthn_widgets",
|