@camstack/addon-auth 1.1.4 → 1.1.5
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-JUeAGU3c.mjs → dist-BopfLZ9P.mjs} +38 -0
- package/dist/{dist-BLBS0d1z.js → dist-biwV9TkV.js} +38 -0
- package/dist/magic-link/auth-magic-link.addon.js +1 -1
- package/dist/magic-link/auth-magic-link.addon.mjs +1 -1
- package/dist/oidc/auth-oidc.addon.js +1 -1
- package/dist/oidc/auth-oidc.addon.mjs +1 -1
- package/dist/webauthn/_stub.js +69 -3
- package/dist/webauthn/{_virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-Bg_T1-iY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-DW5sgIHW.mjs} +2 -2
- package/dist/webauthn/auth-webauthn.addon.js +205 -54
- package/dist/webauthn/auth-webauthn.addon.mjs +205 -54
- package/dist/webauthn/{hostInit-Boe91Eeg.mjs → hostInit-DKNSGMxo.mjs} +2 -2
- package/dist/webauthn/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -16401,6 +16401,19 @@ method(_void(), array(TurnServerSchema).readonly());
|
|
|
16401
16401
|
* b. `finishAuthentication({userId, response})` → server verifies
|
|
16402
16402
|
* the assertion, bumps the credential counter, returns ok.
|
|
16403
16403
|
*
|
|
16404
|
+
* 2b. Usernameless (discoverable-credential) authentication — the
|
|
16405
|
+
* passkey IS the primary factor, no password leg:
|
|
16406
|
+
* a. `beginDiscoverableAuthentication({})` → assertion options with
|
|
16407
|
+
* EMPTY `allowCredentials` (the browser offers every resident
|
|
16408
|
+
* passkey it holds for this RP) + `userVerification: 'required'`
|
|
16409
|
+
* (the passkey replaces both factors, so UV is mandatory).
|
|
16410
|
+
* The challenge is stored server-side, NOT bound to any user.
|
|
16411
|
+
* b. `finishDiscoverableAuthentication({response})` → the provider
|
|
16412
|
+
* resolves the credential by the response's credential id,
|
|
16413
|
+
* verifies the assertion against the stored challenge + that
|
|
16414
|
+
* credential's public key/counter, and returns the OWNING
|
|
16415
|
+
* `userId` — the caller (core auth router) mints the session.
|
|
16416
|
+
*
|
|
16404
16417
|
* 3. Management:
|
|
16405
16418
|
* - `listPasskeys({userId})` — enumerate user's enrolled credentials.
|
|
16406
16419
|
* - `removePasskey({userId, credentialId})` — revoke one credential.
|
|
@@ -16457,6 +16470,19 @@ var userPasskeysCapability = {
|
|
|
16457
16470
|
kind: "mutation",
|
|
16458
16471
|
access: "view"
|
|
16459
16472
|
}),
|
|
16473
|
+
beginDiscoverableAuthentication: method(object({}), object({ optionsJSON: record(string(), unknown()) }), {
|
|
16474
|
+
kind: "mutation",
|
|
16475
|
+
access: "view"
|
|
16476
|
+
}),
|
|
16477
|
+
finishDiscoverableAuthentication: method(object({
|
|
16478
|
+
/** AuthenticationResponseJSON from the browser. */
|
|
16479
|
+
response: record(string(), unknown()) }), object({
|
|
16480
|
+
verified: boolean(),
|
|
16481
|
+
userId: string().nullable()
|
|
16482
|
+
}), {
|
|
16483
|
+
kind: "mutation",
|
|
16484
|
+
access: "view"
|
|
16485
|
+
}),
|
|
16460
16486
|
listPasskeys: method(object({ userId: string() }), array(PasskeySummarySchema), { auth: "admin" }),
|
|
16461
16487
|
removePasskey: method(object({
|
|
16462
16488
|
userId: string(),
|
|
@@ -22959,6 +22985,12 @@ Object.freeze({
|
|
|
22959
22985
|
addonId: null,
|
|
22960
22986
|
access: "view"
|
|
22961
22987
|
},
|
|
22988
|
+
"userPasskeys.beginDiscoverableAuthentication": {
|
|
22989
|
+
capName: "user-passkeys",
|
|
22990
|
+
capScope: "system",
|
|
22991
|
+
addonId: null,
|
|
22992
|
+
access: "view"
|
|
22993
|
+
},
|
|
22962
22994
|
"userPasskeys.beginRegistration": {
|
|
22963
22995
|
capName: "user-passkeys",
|
|
22964
22996
|
capScope: "system",
|
|
@@ -22971,6 +23003,12 @@ Object.freeze({
|
|
|
22971
23003
|
addonId: null,
|
|
22972
23004
|
access: "view"
|
|
22973
23005
|
},
|
|
23006
|
+
"userPasskeys.finishDiscoverableAuthentication": {
|
|
23007
|
+
capName: "user-passkeys",
|
|
23008
|
+
capScope: "system",
|
|
23009
|
+
addonId: null,
|
|
23010
|
+
access: "view"
|
|
23011
|
+
},
|
|
22974
23012
|
"userPasskeys.finishRegistration": {
|
|
22975
23013
|
capName: "user-passkeys",
|
|
22976
23014
|
capScope: "system",
|
|
@@ -16401,6 +16401,19 @@ method(_void(), array(TurnServerSchema).readonly());
|
|
|
16401
16401
|
* b. `finishAuthentication({userId, response})` → server verifies
|
|
16402
16402
|
* the assertion, bumps the credential counter, returns ok.
|
|
16403
16403
|
*
|
|
16404
|
+
* 2b. Usernameless (discoverable-credential) authentication — the
|
|
16405
|
+
* passkey IS the primary factor, no password leg:
|
|
16406
|
+
* a. `beginDiscoverableAuthentication({})` → assertion options with
|
|
16407
|
+
* EMPTY `allowCredentials` (the browser offers every resident
|
|
16408
|
+
* passkey it holds for this RP) + `userVerification: 'required'`
|
|
16409
|
+
* (the passkey replaces both factors, so UV is mandatory).
|
|
16410
|
+
* The challenge is stored server-side, NOT bound to any user.
|
|
16411
|
+
* b. `finishDiscoverableAuthentication({response})` → the provider
|
|
16412
|
+
* resolves the credential by the response's credential id,
|
|
16413
|
+
* verifies the assertion against the stored challenge + that
|
|
16414
|
+
* credential's public key/counter, and returns the OWNING
|
|
16415
|
+
* `userId` — the caller (core auth router) mints the session.
|
|
16416
|
+
*
|
|
16404
16417
|
* 3. Management:
|
|
16405
16418
|
* - `listPasskeys({userId})` — enumerate user's enrolled credentials.
|
|
16406
16419
|
* - `removePasskey({userId, credentialId})` — revoke one credential.
|
|
@@ -16457,6 +16470,19 @@ var userPasskeysCapability = {
|
|
|
16457
16470
|
kind: "mutation",
|
|
16458
16471
|
access: "view"
|
|
16459
16472
|
}),
|
|
16473
|
+
beginDiscoverableAuthentication: method(object({}), object({ optionsJSON: record(string(), unknown()) }), {
|
|
16474
|
+
kind: "mutation",
|
|
16475
|
+
access: "view"
|
|
16476
|
+
}),
|
|
16477
|
+
finishDiscoverableAuthentication: method(object({
|
|
16478
|
+
/** AuthenticationResponseJSON from the browser. */
|
|
16479
|
+
response: record(string(), unknown()) }), object({
|
|
16480
|
+
verified: boolean(),
|
|
16481
|
+
userId: string().nullable()
|
|
16482
|
+
}), {
|
|
16483
|
+
kind: "mutation",
|
|
16484
|
+
access: "view"
|
|
16485
|
+
}),
|
|
16460
16486
|
listPasskeys: method(object({ userId: string() }), array(PasskeySummarySchema), { auth: "admin" }),
|
|
16461
16487
|
removePasskey: method(object({
|
|
16462
16488
|
userId: string(),
|
|
@@ -22959,6 +22985,12 @@ Object.freeze({
|
|
|
22959
22985
|
addonId: null,
|
|
22960
22986
|
access: "view"
|
|
22961
22987
|
},
|
|
22988
|
+
"userPasskeys.beginDiscoverableAuthentication": {
|
|
22989
|
+
capName: "user-passkeys",
|
|
22990
|
+
capScope: "system",
|
|
22991
|
+
addonId: null,
|
|
22992
|
+
access: "view"
|
|
22993
|
+
},
|
|
22962
22994
|
"userPasskeys.beginRegistration": {
|
|
22963
22995
|
capName: "user-passkeys",
|
|
22964
22996
|
capScope: "system",
|
|
@@ -22971,6 +23003,12 @@ Object.freeze({
|
|
|
22971
23003
|
addonId: null,
|
|
22972
23004
|
access: "view"
|
|
22973
23005
|
},
|
|
23006
|
+
"userPasskeys.finishDiscoverableAuthentication": {
|
|
23007
|
+
capName: "user-passkeys",
|
|
23008
|
+
capScope: "system",
|
|
23009
|
+
addonId: null,
|
|
23010
|
+
access: "view"
|
|
23011
|
+
},
|
|
22974
23012
|
"userPasskeys.finishRegistration": {
|
|
22975
23013
|
capName: "user-passkeys",
|
|
22976
23014
|
capScope: "system",
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-biwV9TkV.js");
|
|
7
7
|
//#region src/magic-link/auth-magic-link.addon.ts
|
|
8
8
|
/**
|
|
9
9
|
* Magic-link authentication addon.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-
|
|
1
|
+
import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-BopfLZ9P.mjs";
|
|
2
2
|
//#region src/magic-link/auth-magic-link.addon.ts
|
|
3
3
|
/**
|
|
4
4
|
* Magic-link authentication addon.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-biwV9TkV.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
9
9
|
//#region node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-
|
|
1
|
+
import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-BopfLZ9P.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();
|
package/dist/webauthn/_stub.js
CHANGED
|
@@ -559,10 +559,76 @@ function H(e) {
|
|
|
559
559
|
});
|
|
560
560
|
}
|
|
561
561
|
//#endregion
|
|
562
|
-
//#region src/webauthn/widgets/
|
|
562
|
+
//#region src/webauthn/widgets/direct-login-i18n.ts
|
|
563
563
|
var U = {
|
|
564
|
+
en: {
|
|
565
|
+
signInWithPasskey: "Sign in with a passkey",
|
|
566
|
+
unsupported: "This browser does not support passkeys.",
|
|
567
|
+
failed: "Passkey sign-in failed"
|
|
568
|
+
},
|
|
569
|
+
it: {
|
|
570
|
+
signInWithPasskey: "Accedi con una passkey",
|
|
571
|
+
unsupported: "Questo browser non supporta le passkey.",
|
|
572
|
+
failed: "Accesso con passkey non riuscito"
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
function W(e) {
|
|
576
|
+
return (e ?? globalThis.navigator?.language ?? "en").toLowerCase().split("-")[0] === "it" ? "it" : "en";
|
|
577
|
+
}
|
|
578
|
+
function G(e, t) {
|
|
579
|
+
return U[e][t];
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/webauthn/widgets/PasskeyDirectLogin.tsx
|
|
583
|
+
function K(e) {
|
|
584
|
+
if (!e) return null;
|
|
585
|
+
let t = e.onComplete, n = e.onError, r = e.locale;
|
|
586
|
+
return typeof t == "function" ? {
|
|
587
|
+
onComplete: t,
|
|
588
|
+
onError: typeof n == "function" ? n : void 0,
|
|
589
|
+
locale: typeof r == "string" ? r : void 0
|
|
590
|
+
} : null;
|
|
591
|
+
}
|
|
592
|
+
function q(e) {
|
|
593
|
+
let n = a(), r = K(e.config), [i, c] = t(!1), [l, u] = t(null), d = W(r?.locale);
|
|
594
|
+
return r ? L() ? /* @__PURE__ */ s("div", {
|
|
595
|
+
className: "space-y-2",
|
|
596
|
+
children: [/* @__PURE__ */ s("button", {
|
|
597
|
+
type: "button",
|
|
598
|
+
"data-testid": "login-passkey-direct",
|
|
599
|
+
onClick: async () => {
|
|
600
|
+
u(null), c(!0);
|
|
601
|
+
try {
|
|
602
|
+
let e = await R((await n.passkeyLoginBegin()).optionsJSON), t = await n.passkeyLoginFinish(e);
|
|
603
|
+
r.onComplete(t.token);
|
|
604
|
+
} catch (e) {
|
|
605
|
+
let t = e instanceof Error ? e.message : G(d, "failed");
|
|
606
|
+
u(t), r.onError?.(t);
|
|
607
|
+
} finally {
|
|
608
|
+
c(!1);
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
disabled: i,
|
|
612
|
+
className: "w-full flex items-center justify-center gap-2 rounded-lg border border-border bg-surface px-4 py-2.5 text-sm font-medium text-foreground hover:bg-surface-hover disabled:opacity-50 transition-colors",
|
|
613
|
+
children: [i ? /* @__PURE__ */ o(b, { className: "h-4 w-4 animate-spin text-primary" }) : /* @__PURE__ */ o(v, { className: "h-4 w-4 text-primary" }), G(d, "signInWithPasskey")]
|
|
614
|
+
}), l && /* @__PURE__ */ o("div", {
|
|
615
|
+
className: "rounded-md bg-danger/10 border border-danger/20 px-3 py-2 text-xs text-danger",
|
|
616
|
+
children: l
|
|
617
|
+
})]
|
|
618
|
+
}) : /* @__PURE__ */ o("div", {
|
|
619
|
+
className: "rounded-lg border border-border bg-surface px-3 py-2 text-xs text-foreground-subtle",
|
|
620
|
+
children: G(d, "unsupported")
|
|
621
|
+
}) : /* @__PURE__ */ o("div", {
|
|
622
|
+
className: "rounded-lg border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning",
|
|
623
|
+
children: "Passkey direct-login widget requires an onComplete callback."
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/webauthn/widgets/index.tsx
|
|
628
|
+
var J = {
|
|
564
629
|
"passkey-enrollment": B,
|
|
565
|
-
"passkey-login": H
|
|
630
|
+
"passkey-login": H,
|
|
631
|
+
"passkey-direct-login": q
|
|
566
632
|
};
|
|
567
633
|
//#endregion
|
|
568
|
-
export {
|
|
634
|
+
export { J as default };
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.1.
|
|
6
|
+
version: "1.1.21",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_auth_webauthn_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.1.
|
|
21
|
+
version: "1.1.37",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_auth_webauthn_widgets",
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-biwV9TkV.js");
|
|
7
7
|
let stream = require("stream");
|
|
8
8
|
stream = require_chunk.__toESM(stream, 1);
|
|
9
9
|
let http = require("http");
|
|
@@ -12257,61 +12257,97 @@ var AUTH_WEBAUTHN_REMOTE_NAME = "addon_auth_webauthn_widgets";
|
|
|
12257
12257
|
var AUTH_WEBAUTHN_BUNDLE = "remoteEntry.js";
|
|
12258
12258
|
/** Manifest addon id — the public bundle namespace + login-method addonId. */
|
|
12259
12259
|
var AUTH_WEBAUTHN_ADDON_ID = "auth-webauthn";
|
|
12260
|
-
var authWebauthnWidgets = [
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
tab: "dashboard",
|
|
12289
|
-
label: "Passkey login",
|
|
12290
|
-
kind: "remote",
|
|
12291
|
-
remote: {
|
|
12292
|
-
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12293
|
-
exposedModule: "./widgets",
|
|
12294
|
-
componentKey: "passkey-login"
|
|
12260
|
+
var authWebauthnWidgets = [
|
|
12261
|
+
{
|
|
12262
|
+
tab: "dashboard",
|
|
12263
|
+
label: "Passkeys",
|
|
12264
|
+
kind: "remote",
|
|
12265
|
+
remote: {
|
|
12266
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12267
|
+
exposedModule: "./widgets",
|
|
12268
|
+
componentKey: "passkey-enrollment"
|
|
12269
|
+
},
|
|
12270
|
+
stableId: "passkey-enrollment",
|
|
12271
|
+
description: "Enroll and manage passkeys (WebAuthn) for your account.",
|
|
12272
|
+
icon: "fingerprint",
|
|
12273
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12274
|
+
hosts: ["dashboard"],
|
|
12275
|
+
requires: {
|
|
12276
|
+
deviceContext: false,
|
|
12277
|
+
integrationContext: false
|
|
12278
|
+
},
|
|
12279
|
+
defaultSize: "lg",
|
|
12280
|
+
allowedSizes: [
|
|
12281
|
+
"md",
|
|
12282
|
+
"lg",
|
|
12283
|
+
"xl"
|
|
12284
|
+
],
|
|
12285
|
+
defaultColumns: 12,
|
|
12286
|
+
defaultRows: 3,
|
|
12287
|
+
preAuth: false
|
|
12295
12288
|
},
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12289
|
+
{
|
|
12290
|
+
tab: "dashboard",
|
|
12291
|
+
label: "Passkey login",
|
|
12292
|
+
kind: "remote",
|
|
12293
|
+
remote: {
|
|
12294
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12295
|
+
exposedModule: "./widgets",
|
|
12296
|
+
componentKey: "passkey-login"
|
|
12297
|
+
},
|
|
12298
|
+
stableId: "passkey-login",
|
|
12299
|
+
description: "Login-page passkey second-factor ceremony.",
|
|
12300
|
+
icon: "fingerprint",
|
|
12301
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12302
|
+
hosts: ["dashboard"],
|
|
12303
|
+
requires: {
|
|
12304
|
+
deviceContext: false,
|
|
12305
|
+
integrationContext: false
|
|
12306
|
+
},
|
|
12307
|
+
defaultSize: "sm",
|
|
12308
|
+
allowedSizes: ["sm"],
|
|
12309
|
+
defaultColumns: 4,
|
|
12310
|
+
defaultRows: 1,
|
|
12311
|
+
preAuth: true
|
|
12304
12312
|
},
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12313
|
+
{
|
|
12314
|
+
tab: "dashboard",
|
|
12315
|
+
label: "Passkey sign-in",
|
|
12316
|
+
kind: "remote",
|
|
12317
|
+
remote: {
|
|
12318
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12319
|
+
exposedModule: "./widgets",
|
|
12320
|
+
componentKey: "passkey-direct-login"
|
|
12321
|
+
},
|
|
12322
|
+
stableId: "passkey-direct-login",
|
|
12323
|
+
description: "Login-page usernameless (discoverable-credential) passkey sign-in.",
|
|
12324
|
+
icon: "fingerprint",
|
|
12325
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12326
|
+
hosts: ["dashboard"],
|
|
12327
|
+
requires: {
|
|
12328
|
+
deviceContext: false,
|
|
12329
|
+
integrationContext: false
|
|
12330
|
+
},
|
|
12331
|
+
defaultSize: "sm",
|
|
12332
|
+
allowedSizes: ["sm"],
|
|
12333
|
+
defaultColumns: 4,
|
|
12334
|
+
defaultRows: 1,
|
|
12335
|
+
preAuth: true
|
|
12336
|
+
}
|
|
12337
|
+
];
|
|
12311
12338
|
/**
|
|
12312
|
-
* Login-method
|
|
12313
|
-
*
|
|
12314
|
-
*
|
|
12339
|
+
* Login-method contributions — aggregated by the public
|
|
12340
|
+
* `auth.listLoginMethods` procedure, which stamps a public `bundleUrl`
|
|
12341
|
+
* from `addonId` + `bundle`.
|
|
12342
|
+
*
|
|
12343
|
+
* - `passkey-login` (second-factor) — the post-password 2FA ceremony,
|
|
12344
|
+
* mounted only when the challenged user has the `passkey` factor.
|
|
12345
|
+
* - `passkey-direct-login` (primary) — usernameless sign-in via the
|
|
12346
|
+
* standard WebAuthn discoverable-credentials flow. Contributed
|
|
12347
|
+
* unconditionally while the addon is registered: the browser's own
|
|
12348
|
+
* passkey picker is the honest gate (it simply offers nothing when
|
|
12349
|
+
* no resident credential matches the RP) — probing the server for
|
|
12350
|
+
* "does anyone have a passkey" pre-auth would leak enrollment state.
|
|
12315
12351
|
*/
|
|
12316
12352
|
var authWebauthnLoginMethods = [{
|
|
12317
12353
|
kind: "widget",
|
|
@@ -12324,6 +12360,17 @@ var authWebauthnLoginMethods = [{
|
|
|
12324
12360
|
componentKey: "passkey-login"
|
|
12325
12361
|
},
|
|
12326
12362
|
stage: "second-factor"
|
|
12363
|
+
}, {
|
|
12364
|
+
kind: "widget",
|
|
12365
|
+
id: `${AUTH_WEBAUTHN_ADDON_ID}/passkey-direct-login`,
|
|
12366
|
+
addonId: AUTH_WEBAUTHN_ADDON_ID,
|
|
12367
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12368
|
+
remote: {
|
|
12369
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12370
|
+
exposedModule: "./widgets",
|
|
12371
|
+
componentKey: "passkey-direct-login"
|
|
12372
|
+
},
|
|
12373
|
+
stage: "primary"
|
|
12327
12374
|
}];
|
|
12328
12375
|
//#endregion
|
|
12329
12376
|
//#region src/webauthn/auth-webauthn.addon.ts
|
|
@@ -12388,6 +12435,8 @@ var AuthWebauthnAddon = class extends require_dist.BaseAddon {
|
|
|
12388
12435
|
finishRegistration: async ({ userId, response, label }) => this.finishRegistration(userId, response, label),
|
|
12389
12436
|
beginAuthentication: async ({ userId }) => this.beginAuthentication(userId),
|
|
12390
12437
|
finishAuthentication: async ({ userId, response }) => this.finishAuthentication(userId, response),
|
|
12438
|
+
beginDiscoverableAuthentication: async () => this.beginDiscoverableAuthentication(),
|
|
12439
|
+
finishDiscoverableAuthentication: async ({ response }) => this.finishDiscoverableAuthentication(response),
|
|
12391
12440
|
listPasskeys: async ({ userId }) => this.listPasskeys(userId),
|
|
12392
12441
|
removePasskey: async ({ userId, credentialId }) => this.removePasskey(userId, credentialId)
|
|
12393
12442
|
};
|
|
@@ -12505,9 +12554,12 @@ var AuthWebauthnAddon = class extends require_dist.BaseAddon {
|
|
|
12505
12554
|
if (this.config.origin && this.config.origin.trim()) return this.config.origin.trim().replace(/\/+$/, "");
|
|
12506
12555
|
return (process.env["CAMSTACK_PUBLIC_ORIGIN"] || "http://localhost:4443").replace(/\/+$/, "");
|
|
12507
12556
|
}
|
|
12557
|
+
challengeTtlMs() {
|
|
12558
|
+
return (this.config.challengeTtlSec || DEFAULT_CONFIG.challengeTtlSec) * 1e3;
|
|
12559
|
+
}
|
|
12508
12560
|
pruneExpiredChallenges() {
|
|
12509
12561
|
const now = Date.now();
|
|
12510
|
-
const ttlMs =
|
|
12562
|
+
const ttlMs = this.challengeTtlMs();
|
|
12511
12563
|
for (const [k, v] of this.pending.entries()) if (now - v.createdAt > ttlMs) this.pending.delete(k);
|
|
12512
12564
|
}
|
|
12513
12565
|
base64urlEncode(bytes) {
|
|
@@ -12633,6 +12685,105 @@ var AuthWebauthnAddon = class extends require_dist.BaseAddon {
|
|
|
12633
12685
|
await this.store.updateCounter(credentialId, verification.authenticationInfo.newCounter);
|
|
12634
12686
|
return { verified: true };
|
|
12635
12687
|
}
|
|
12688
|
+
/**
|
|
12689
|
+
* Usernameless (discoverable-credential) login — leg 1. Issues
|
|
12690
|
+
* assertion options with an EMPTY `allowCredentials` list so the
|
|
12691
|
+
* browser offers every resident passkey it holds for this RP, and
|
|
12692
|
+
* `userVerification: 'required'` because the passkey is the ONLY
|
|
12693
|
+
* factor (it replaces both username/password and the 2FA leg).
|
|
12694
|
+
* The challenge is stored server-side keyed by its own value and is
|
|
12695
|
+
* NOT bound to any user — `finishDiscoverableAuthentication` resolves
|
|
12696
|
+
* the owner from the asserted credential id.
|
|
12697
|
+
*/
|
|
12698
|
+
async beginDiscoverableAuthentication() {
|
|
12699
|
+
if (!this.store) throw new Error("passkey store unavailable");
|
|
12700
|
+
this.pruneExpiredChallenges();
|
|
12701
|
+
const options = await generateAuthenticationOptions({
|
|
12702
|
+
rpID: this.resolveRpID(),
|
|
12703
|
+
allowCredentials: [],
|
|
12704
|
+
userVerification: "required"
|
|
12705
|
+
});
|
|
12706
|
+
this.pending.set(options.challenge, {
|
|
12707
|
+
challenge: options.challenge,
|
|
12708
|
+
userId: "",
|
|
12709
|
+
kind: "discoverable-authentication",
|
|
12710
|
+
createdAt: Date.now()
|
|
12711
|
+
});
|
|
12712
|
+
return { optionsJSON: options };
|
|
12713
|
+
}
|
|
12714
|
+
/**
|
|
12715
|
+
* Usernameless login — leg 2. Resolves the credential by the
|
|
12716
|
+
* response's credential id, verifies the assertion against the stored
|
|
12717
|
+
* challenge + that credential's public key/counter, and returns the
|
|
12718
|
+
* OWNING userId. Fail-closed: every anomaly returns
|
|
12719
|
+
* `{ verified: false, userId: null }`.
|
|
12720
|
+
*
|
|
12721
|
+
* Replay protection: the pending challenge is consumed (deleted)
|
|
12722
|
+
* BEFORE verification, so an assertion can be spent exactly once.
|
|
12723
|
+
* The TTL is enforced here too — pruning alone only runs on begin
|
|
12724
|
+
* legs, which an attacker controls the timing of.
|
|
12725
|
+
*/
|
|
12726
|
+
async finishDiscoverableAuthentication(response) {
|
|
12727
|
+
if (!this.store) return {
|
|
12728
|
+
verified: false,
|
|
12729
|
+
userId: null
|
|
12730
|
+
};
|
|
12731
|
+
const challenge = this.decodeClientData(response)?.challenge;
|
|
12732
|
+
if (!challenge) return {
|
|
12733
|
+
verified: false,
|
|
12734
|
+
userId: null
|
|
12735
|
+
};
|
|
12736
|
+
const pending = this.pending.get(challenge);
|
|
12737
|
+
if (!pending || pending.kind !== "discoverable-authentication") return {
|
|
12738
|
+
verified: false,
|
|
12739
|
+
userId: null
|
|
12740
|
+
};
|
|
12741
|
+
this.pending.delete(challenge);
|
|
12742
|
+
if (Date.now() - pending.createdAt > this.challengeTtlMs()) return {
|
|
12743
|
+
verified: false,
|
|
12744
|
+
userId: null
|
|
12745
|
+
};
|
|
12746
|
+
const credentialId = typeof response["id"] === "string" ? response["id"] : "";
|
|
12747
|
+
const stored = await this.store.findByCredentialId(credentialId);
|
|
12748
|
+
if (!stored) return {
|
|
12749
|
+
verified: false,
|
|
12750
|
+
userId: null
|
|
12751
|
+
};
|
|
12752
|
+
let verification;
|
|
12753
|
+
try {
|
|
12754
|
+
verification = await verifyAuthenticationResponse({
|
|
12755
|
+
response,
|
|
12756
|
+
expectedChallenge: challenge,
|
|
12757
|
+
expectedRPID: this.resolveRpID(),
|
|
12758
|
+
expectedOrigin: this.resolveOrigin(),
|
|
12759
|
+
requireUserVerification: true,
|
|
12760
|
+
credential: {
|
|
12761
|
+
id: stored.credentialId,
|
|
12762
|
+
publicKey: this.base64urlDecode(stored.publicKey),
|
|
12763
|
+
counter: stored.counter,
|
|
12764
|
+
transports: stored.transports
|
|
12765
|
+
}
|
|
12766
|
+
});
|
|
12767
|
+
} catch (err) {
|
|
12768
|
+
this.ctx.logger.warn("Discoverable passkey verification threw", { meta: {
|
|
12769
|
+
credentialId,
|
|
12770
|
+
err: err instanceof Error ? err.message : String(err)
|
|
12771
|
+
} });
|
|
12772
|
+
return {
|
|
12773
|
+
verified: false,
|
|
12774
|
+
userId: null
|
|
12775
|
+
};
|
|
12776
|
+
}
|
|
12777
|
+
if (!verification.verified || !verification.authenticationInfo) return {
|
|
12778
|
+
verified: false,
|
|
12779
|
+
userId: null
|
|
12780
|
+
};
|
|
12781
|
+
await this.store.updateCounter(credentialId, verification.authenticationInfo.newCounter);
|
|
12782
|
+
return {
|
|
12783
|
+
verified: true,
|
|
12784
|
+
userId: stored.userId
|
|
12785
|
+
};
|
|
12786
|
+
}
|
|
12636
12787
|
async listPasskeys(userId) {
|
|
12637
12788
|
if (!this.store) return [];
|
|
12638
12789
|
return (await this.store.findByUserId(userId)).map((r) => ({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as loginMethodCapability, c as BaseAddon, d as string, l as array, n as addonWidgetsSourceCapability, o as userPasskeysCapability, u as object } from "../dist-
|
|
1
|
+
import { a as loginMethodCapability, c as BaseAddon, d as string, l as array, n as addonWidgetsSourceCapability, o as userPasskeysCapability, u as object } from "../dist-BopfLZ9P.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import Stream from "stream";
|
|
4
4
|
import http from "http";
|
|
@@ -12283,61 +12283,97 @@ var AUTH_WEBAUTHN_REMOTE_NAME = "addon_auth_webauthn_widgets";
|
|
|
12283
12283
|
var AUTH_WEBAUTHN_BUNDLE = "remoteEntry.js";
|
|
12284
12284
|
/** Manifest addon id — the public bundle namespace + login-method addonId. */
|
|
12285
12285
|
var AUTH_WEBAUTHN_ADDON_ID = "auth-webauthn";
|
|
12286
|
-
var authWebauthnWidgets = [
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
tab: "dashboard",
|
|
12315
|
-
label: "Passkey login",
|
|
12316
|
-
kind: "remote",
|
|
12317
|
-
remote: {
|
|
12318
|
-
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12319
|
-
exposedModule: "./widgets",
|
|
12320
|
-
componentKey: "passkey-login"
|
|
12286
|
+
var authWebauthnWidgets = [
|
|
12287
|
+
{
|
|
12288
|
+
tab: "dashboard",
|
|
12289
|
+
label: "Passkeys",
|
|
12290
|
+
kind: "remote",
|
|
12291
|
+
remote: {
|
|
12292
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12293
|
+
exposedModule: "./widgets",
|
|
12294
|
+
componentKey: "passkey-enrollment"
|
|
12295
|
+
},
|
|
12296
|
+
stableId: "passkey-enrollment",
|
|
12297
|
+
description: "Enroll and manage passkeys (WebAuthn) for your account.",
|
|
12298
|
+
icon: "fingerprint",
|
|
12299
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12300
|
+
hosts: ["dashboard"],
|
|
12301
|
+
requires: {
|
|
12302
|
+
deviceContext: false,
|
|
12303
|
+
integrationContext: false
|
|
12304
|
+
},
|
|
12305
|
+
defaultSize: "lg",
|
|
12306
|
+
allowedSizes: [
|
|
12307
|
+
"md",
|
|
12308
|
+
"lg",
|
|
12309
|
+
"xl"
|
|
12310
|
+
],
|
|
12311
|
+
defaultColumns: 12,
|
|
12312
|
+
defaultRows: 3,
|
|
12313
|
+
preAuth: false
|
|
12321
12314
|
},
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12315
|
+
{
|
|
12316
|
+
tab: "dashboard",
|
|
12317
|
+
label: "Passkey login",
|
|
12318
|
+
kind: "remote",
|
|
12319
|
+
remote: {
|
|
12320
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12321
|
+
exposedModule: "./widgets",
|
|
12322
|
+
componentKey: "passkey-login"
|
|
12323
|
+
},
|
|
12324
|
+
stableId: "passkey-login",
|
|
12325
|
+
description: "Login-page passkey second-factor ceremony.",
|
|
12326
|
+
icon: "fingerprint",
|
|
12327
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12328
|
+
hosts: ["dashboard"],
|
|
12329
|
+
requires: {
|
|
12330
|
+
deviceContext: false,
|
|
12331
|
+
integrationContext: false
|
|
12332
|
+
},
|
|
12333
|
+
defaultSize: "sm",
|
|
12334
|
+
allowedSizes: ["sm"],
|
|
12335
|
+
defaultColumns: 4,
|
|
12336
|
+
defaultRows: 1,
|
|
12337
|
+
preAuth: true
|
|
12330
12338
|
},
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12339
|
+
{
|
|
12340
|
+
tab: "dashboard",
|
|
12341
|
+
label: "Passkey sign-in",
|
|
12342
|
+
kind: "remote",
|
|
12343
|
+
remote: {
|
|
12344
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12345
|
+
exposedModule: "./widgets",
|
|
12346
|
+
componentKey: "passkey-direct-login"
|
|
12347
|
+
},
|
|
12348
|
+
stableId: "passkey-direct-login",
|
|
12349
|
+
description: "Login-page usernameless (discoverable-credential) passkey sign-in.",
|
|
12350
|
+
icon: "fingerprint",
|
|
12351
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12352
|
+
hosts: ["dashboard"],
|
|
12353
|
+
requires: {
|
|
12354
|
+
deviceContext: false,
|
|
12355
|
+
integrationContext: false
|
|
12356
|
+
},
|
|
12357
|
+
defaultSize: "sm",
|
|
12358
|
+
allowedSizes: ["sm"],
|
|
12359
|
+
defaultColumns: 4,
|
|
12360
|
+
defaultRows: 1,
|
|
12361
|
+
preAuth: true
|
|
12362
|
+
}
|
|
12363
|
+
];
|
|
12337
12364
|
/**
|
|
12338
|
-
* Login-method
|
|
12339
|
-
*
|
|
12340
|
-
*
|
|
12365
|
+
* Login-method contributions — aggregated by the public
|
|
12366
|
+
* `auth.listLoginMethods` procedure, which stamps a public `bundleUrl`
|
|
12367
|
+
* from `addonId` + `bundle`.
|
|
12368
|
+
*
|
|
12369
|
+
* - `passkey-login` (second-factor) — the post-password 2FA ceremony,
|
|
12370
|
+
* mounted only when the challenged user has the `passkey` factor.
|
|
12371
|
+
* - `passkey-direct-login` (primary) — usernameless sign-in via the
|
|
12372
|
+
* standard WebAuthn discoverable-credentials flow. Contributed
|
|
12373
|
+
* unconditionally while the addon is registered: the browser's own
|
|
12374
|
+
* passkey picker is the honest gate (it simply offers nothing when
|
|
12375
|
+
* no resident credential matches the RP) — probing the server for
|
|
12376
|
+
* "does anyone have a passkey" pre-auth would leak enrollment state.
|
|
12341
12377
|
*/
|
|
12342
12378
|
var authWebauthnLoginMethods = [{
|
|
12343
12379
|
kind: "widget",
|
|
@@ -12350,6 +12386,17 @@ var authWebauthnLoginMethods = [{
|
|
|
12350
12386
|
componentKey: "passkey-login"
|
|
12351
12387
|
},
|
|
12352
12388
|
stage: "second-factor"
|
|
12389
|
+
}, {
|
|
12390
|
+
kind: "widget",
|
|
12391
|
+
id: `${AUTH_WEBAUTHN_ADDON_ID}/passkey-direct-login`,
|
|
12392
|
+
addonId: AUTH_WEBAUTHN_ADDON_ID,
|
|
12393
|
+
bundle: AUTH_WEBAUTHN_BUNDLE,
|
|
12394
|
+
remote: {
|
|
12395
|
+
remoteName: AUTH_WEBAUTHN_REMOTE_NAME,
|
|
12396
|
+
exposedModule: "./widgets",
|
|
12397
|
+
componentKey: "passkey-direct-login"
|
|
12398
|
+
},
|
|
12399
|
+
stage: "primary"
|
|
12353
12400
|
}];
|
|
12354
12401
|
var EndpointsSchema = array(object({
|
|
12355
12402
|
hostname: string(),
|
|
@@ -12377,6 +12424,8 @@ var AuthWebauthnAddon = class extends BaseAddon {
|
|
|
12377
12424
|
finishRegistration: async ({ userId, response, label }) => this.finishRegistration(userId, response, label),
|
|
12378
12425
|
beginAuthentication: async ({ userId }) => this.beginAuthentication(userId),
|
|
12379
12426
|
finishAuthentication: async ({ userId, response }) => this.finishAuthentication(userId, response),
|
|
12427
|
+
beginDiscoverableAuthentication: async () => this.beginDiscoverableAuthentication(),
|
|
12428
|
+
finishDiscoverableAuthentication: async ({ response }) => this.finishDiscoverableAuthentication(response),
|
|
12380
12429
|
listPasskeys: async ({ userId }) => this.listPasskeys(userId),
|
|
12381
12430
|
removePasskey: async ({ userId, credentialId }) => this.removePasskey(userId, credentialId)
|
|
12382
12431
|
};
|
|
@@ -12494,9 +12543,12 @@ var AuthWebauthnAddon = class extends BaseAddon {
|
|
|
12494
12543
|
if (this.config.origin && this.config.origin.trim()) return this.config.origin.trim().replace(/\/+$/, "");
|
|
12495
12544
|
return (process.env["CAMSTACK_PUBLIC_ORIGIN"] || "http://localhost:4443").replace(/\/+$/, "");
|
|
12496
12545
|
}
|
|
12546
|
+
challengeTtlMs() {
|
|
12547
|
+
return (this.config.challengeTtlSec || DEFAULT_CONFIG.challengeTtlSec) * 1e3;
|
|
12548
|
+
}
|
|
12497
12549
|
pruneExpiredChallenges() {
|
|
12498
12550
|
const now = Date.now();
|
|
12499
|
-
const ttlMs =
|
|
12551
|
+
const ttlMs = this.challengeTtlMs();
|
|
12500
12552
|
for (const [k, v] of this.pending.entries()) if (now - v.createdAt > ttlMs) this.pending.delete(k);
|
|
12501
12553
|
}
|
|
12502
12554
|
base64urlEncode(bytes) {
|
|
@@ -12622,6 +12674,105 @@ var AuthWebauthnAddon = class extends BaseAddon {
|
|
|
12622
12674
|
await this.store.updateCounter(credentialId, verification.authenticationInfo.newCounter);
|
|
12623
12675
|
return { verified: true };
|
|
12624
12676
|
}
|
|
12677
|
+
/**
|
|
12678
|
+
* Usernameless (discoverable-credential) login — leg 1. Issues
|
|
12679
|
+
* assertion options with an EMPTY `allowCredentials` list so the
|
|
12680
|
+
* browser offers every resident passkey it holds for this RP, and
|
|
12681
|
+
* `userVerification: 'required'` because the passkey is the ONLY
|
|
12682
|
+
* factor (it replaces both username/password and the 2FA leg).
|
|
12683
|
+
* The challenge is stored server-side keyed by its own value and is
|
|
12684
|
+
* NOT bound to any user — `finishDiscoverableAuthentication` resolves
|
|
12685
|
+
* the owner from the asserted credential id.
|
|
12686
|
+
*/
|
|
12687
|
+
async beginDiscoverableAuthentication() {
|
|
12688
|
+
if (!this.store) throw new Error("passkey store unavailable");
|
|
12689
|
+
this.pruneExpiredChallenges();
|
|
12690
|
+
const options = await generateAuthenticationOptions({
|
|
12691
|
+
rpID: this.resolveRpID(),
|
|
12692
|
+
allowCredentials: [],
|
|
12693
|
+
userVerification: "required"
|
|
12694
|
+
});
|
|
12695
|
+
this.pending.set(options.challenge, {
|
|
12696
|
+
challenge: options.challenge,
|
|
12697
|
+
userId: "",
|
|
12698
|
+
kind: "discoverable-authentication",
|
|
12699
|
+
createdAt: Date.now()
|
|
12700
|
+
});
|
|
12701
|
+
return { optionsJSON: options };
|
|
12702
|
+
}
|
|
12703
|
+
/**
|
|
12704
|
+
* Usernameless login — leg 2. Resolves the credential by the
|
|
12705
|
+
* response's credential id, verifies the assertion against the stored
|
|
12706
|
+
* challenge + that credential's public key/counter, and returns the
|
|
12707
|
+
* OWNING userId. Fail-closed: every anomaly returns
|
|
12708
|
+
* `{ verified: false, userId: null }`.
|
|
12709
|
+
*
|
|
12710
|
+
* Replay protection: the pending challenge is consumed (deleted)
|
|
12711
|
+
* BEFORE verification, so an assertion can be spent exactly once.
|
|
12712
|
+
* The TTL is enforced here too — pruning alone only runs on begin
|
|
12713
|
+
* legs, which an attacker controls the timing of.
|
|
12714
|
+
*/
|
|
12715
|
+
async finishDiscoverableAuthentication(response) {
|
|
12716
|
+
if (!this.store) return {
|
|
12717
|
+
verified: false,
|
|
12718
|
+
userId: null
|
|
12719
|
+
};
|
|
12720
|
+
const challenge = this.decodeClientData(response)?.challenge;
|
|
12721
|
+
if (!challenge) return {
|
|
12722
|
+
verified: false,
|
|
12723
|
+
userId: null
|
|
12724
|
+
};
|
|
12725
|
+
const pending = this.pending.get(challenge);
|
|
12726
|
+
if (!pending || pending.kind !== "discoverable-authentication") return {
|
|
12727
|
+
verified: false,
|
|
12728
|
+
userId: null
|
|
12729
|
+
};
|
|
12730
|
+
this.pending.delete(challenge);
|
|
12731
|
+
if (Date.now() - pending.createdAt > this.challengeTtlMs()) return {
|
|
12732
|
+
verified: false,
|
|
12733
|
+
userId: null
|
|
12734
|
+
};
|
|
12735
|
+
const credentialId = typeof response["id"] === "string" ? response["id"] : "";
|
|
12736
|
+
const stored = await this.store.findByCredentialId(credentialId);
|
|
12737
|
+
if (!stored) return {
|
|
12738
|
+
verified: false,
|
|
12739
|
+
userId: null
|
|
12740
|
+
};
|
|
12741
|
+
let verification;
|
|
12742
|
+
try {
|
|
12743
|
+
verification = await verifyAuthenticationResponse({
|
|
12744
|
+
response,
|
|
12745
|
+
expectedChallenge: challenge,
|
|
12746
|
+
expectedRPID: this.resolveRpID(),
|
|
12747
|
+
expectedOrigin: this.resolveOrigin(),
|
|
12748
|
+
requireUserVerification: true,
|
|
12749
|
+
credential: {
|
|
12750
|
+
id: stored.credentialId,
|
|
12751
|
+
publicKey: this.base64urlDecode(stored.publicKey),
|
|
12752
|
+
counter: stored.counter,
|
|
12753
|
+
transports: stored.transports
|
|
12754
|
+
}
|
|
12755
|
+
});
|
|
12756
|
+
} catch (err) {
|
|
12757
|
+
this.ctx.logger.warn("Discoverable passkey verification threw", { meta: {
|
|
12758
|
+
credentialId,
|
|
12759
|
+
err: err instanceof Error ? err.message : String(err)
|
|
12760
|
+
} });
|
|
12761
|
+
return {
|
|
12762
|
+
verified: false,
|
|
12763
|
+
userId: null
|
|
12764
|
+
};
|
|
12765
|
+
}
|
|
12766
|
+
if (!verification.verified || !verification.authenticationInfo) return {
|
|
12767
|
+
verified: false,
|
|
12768
|
+
userId: null
|
|
12769
|
+
};
|
|
12770
|
+
await this.store.updateCounter(credentialId, verification.authenticationInfo.newCounter);
|
|
12771
|
+
return {
|
|
12772
|
+
verified: true,
|
|
12773
|
+
userId: stored.userId
|
|
12774
|
+
};
|
|
12775
|
+
}
|
|
12625
12776
|
async listPasskeys(userId) {
|
|
12626
12777
|
if (!this.store) return [];
|
|
12627
12778
|
return (await this.store.findByUserId(userId)).map((r) => ({
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.1.
|
|
39
|
+
version: "1.1.37",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.1.
|
|
48
|
+
version: "1.1.21",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-DW5sgIHW.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-auth",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "CamStack authentication bundle — magic-link, OIDC, and WebAuthn/passkey. Multi-entry npm package shipping 3 addons under a single bundle; each addon keeps its own id, capabilities, and runner.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|