@waaskey/sdk 0.4.0 → 0.4.2
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.cjs +53 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +53 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1044,6 +1044,23 @@ interface WalletBackupParams {
|
|
|
1044
1044
|
salt: string;
|
|
1045
1045
|
credentialId: string;
|
|
1046
1046
|
};
|
|
1047
|
+
/**
|
|
1048
|
+
* Enrol the passkey as a recovery FACTOR **in place of** the recovery code (#511).
|
|
1049
|
+
*
|
|
1050
|
+
* The wrap (`passkey` above) decides what can OPEN the backup; a factor decides what the server
|
|
1051
|
+
* demands before releasing it. Enrolling the passkey as a factor is the difference between "the
|
|
1052
|
+
* code is still mandatory" and a user who never has to write one down.
|
|
1053
|
+
*
|
|
1054
|
+
* It REPLACES `recovery_code` rather than adding to it, because the gate is an AND of every enrolled
|
|
1055
|
+
* factor: enrolling both would mean losing EITHER the code or the passkey makes the wallet
|
|
1056
|
+
* unrecoverable — strictly worse than either alone.
|
|
1057
|
+
*
|
|
1058
|
+
* Requires `passkey`, and that credential must ALREADY be registered with the platform (the API
|
|
1059
|
+
* refuses an unknown credential id — a factor nobody can satisfy is a lock-out discovered at the
|
|
1060
|
+
* worst possible moment). Defaults to false: trading a written code for a synced authenticator is
|
|
1061
|
+
* the app's call, not the SDK's.
|
|
1062
|
+
*/
|
|
1063
|
+
passkeyFactor?: boolean;
|
|
1047
1064
|
}
|
|
1048
1065
|
/** Options for `wallets.joinCeremony(...)` (#349). */
|
|
1049
1066
|
interface JoinCeremonyOptions {
|
|
@@ -1679,6 +1696,23 @@ interface RecoveryChallengeResponse {
|
|
|
1679
1696
|
* and single-use with this session — feed it to `passkeyFactorVerification`.
|
|
1680
1697
|
*/
|
|
1681
1698
|
passkeyChallenge?: string;
|
|
1699
|
+
/**
|
|
1700
|
+
* The non-secret inputs that reproduce the backup's PRF key — present only when the backup carries
|
|
1701
|
+
* a `passkey_prf` wrap (#510).
|
|
1702
|
+
*
|
|
1703
|
+
* Pass `salt` + `credentialId` to `PasskeyPrfSecretProvider.unlock(...)` to obtain the
|
|
1704
|
+
* `passkeySecret` that {@link RecoverParams} takes. They arrive HERE, on the challenge, because the
|
|
1705
|
+
* salt is an INPUT to the authenticator ceremony while the wraps themselves are released only after
|
|
1706
|
+
* every factor passes — so a device that lost its local storage (which is the recovery case) would
|
|
1707
|
+
* otherwise have no way to use its passkey at all.
|
|
1708
|
+
*
|
|
1709
|
+
* Neither value is a secret: the credential id is public by WebAuthn design, and the salt is
|
|
1710
|
+
* meaningless without the authenticator and a user-verified ceremony.
|
|
1711
|
+
*/
|
|
1712
|
+
passkeyWrap?: {
|
|
1713
|
+
salt: string;
|
|
1714
|
+
credentialId: string;
|
|
1715
|
+
};
|
|
1682
1716
|
}
|
|
1683
1717
|
/**
|
|
1684
1718
|
* How the key that opens a backup was wrapped (backend `RecoveryKeyWrap`, #510).
|
|
@@ -1737,6 +1771,23 @@ interface RegisterRecoveryParams {
|
|
|
1737
1771
|
salt: string;
|
|
1738
1772
|
credentialId: string;
|
|
1739
1773
|
};
|
|
1774
|
+
/**
|
|
1775
|
+
* Enrol the passkey as a recovery FACTOR **in place of** the recovery code (#511).
|
|
1776
|
+
*
|
|
1777
|
+
* The wrap (`passkey` above) decides what can OPEN the backup; a factor decides what the server
|
|
1778
|
+
* demands before releasing it. Enrolling the passkey as a factor is the difference between "the
|
|
1779
|
+
* code is still mandatory" and a user who never has to write one down.
|
|
1780
|
+
*
|
|
1781
|
+
* It REPLACES `recovery_code` rather than adding to it, because the gate is an AND of every enrolled
|
|
1782
|
+
* factor: enrolling both would mean losing EITHER the code or the passkey makes the wallet
|
|
1783
|
+
* unrecoverable — strictly worse than either alone.
|
|
1784
|
+
*
|
|
1785
|
+
* Requires `passkey`, and that credential must ALREADY be registered with the platform (the API
|
|
1786
|
+
* refuses an unknown credential id — a factor nobody can satisfy is a lock-out discovered at the
|
|
1787
|
+
* worst possible moment). Defaults to false: trading a written code for a synced authenticator is
|
|
1788
|
+
* the app's call, not the SDK's.
|
|
1789
|
+
*/
|
|
1790
|
+
passkeyFactor?: boolean;
|
|
1740
1791
|
}
|
|
1741
1792
|
/** Parameters for `recovery.recover(...)` / `recovery.retrieveShare(...)`. */
|
|
1742
1793
|
interface RecoverParams {
|
package/dist/index.d.ts
CHANGED
|
@@ -1044,6 +1044,23 @@ interface WalletBackupParams {
|
|
|
1044
1044
|
salt: string;
|
|
1045
1045
|
credentialId: string;
|
|
1046
1046
|
};
|
|
1047
|
+
/**
|
|
1048
|
+
* Enrol the passkey as a recovery FACTOR **in place of** the recovery code (#511).
|
|
1049
|
+
*
|
|
1050
|
+
* The wrap (`passkey` above) decides what can OPEN the backup; a factor decides what the server
|
|
1051
|
+
* demands before releasing it. Enrolling the passkey as a factor is the difference between "the
|
|
1052
|
+
* code is still mandatory" and a user who never has to write one down.
|
|
1053
|
+
*
|
|
1054
|
+
* It REPLACES `recovery_code` rather than adding to it, because the gate is an AND of every enrolled
|
|
1055
|
+
* factor: enrolling both would mean losing EITHER the code or the passkey makes the wallet
|
|
1056
|
+
* unrecoverable — strictly worse than either alone.
|
|
1057
|
+
*
|
|
1058
|
+
* Requires `passkey`, and that credential must ALREADY be registered with the platform (the API
|
|
1059
|
+
* refuses an unknown credential id — a factor nobody can satisfy is a lock-out discovered at the
|
|
1060
|
+
* worst possible moment). Defaults to false: trading a written code for a synced authenticator is
|
|
1061
|
+
* the app's call, not the SDK's.
|
|
1062
|
+
*/
|
|
1063
|
+
passkeyFactor?: boolean;
|
|
1047
1064
|
}
|
|
1048
1065
|
/** Options for `wallets.joinCeremony(...)` (#349). */
|
|
1049
1066
|
interface JoinCeremonyOptions {
|
|
@@ -1679,6 +1696,23 @@ interface RecoveryChallengeResponse {
|
|
|
1679
1696
|
* and single-use with this session — feed it to `passkeyFactorVerification`.
|
|
1680
1697
|
*/
|
|
1681
1698
|
passkeyChallenge?: string;
|
|
1699
|
+
/**
|
|
1700
|
+
* The non-secret inputs that reproduce the backup's PRF key — present only when the backup carries
|
|
1701
|
+
* a `passkey_prf` wrap (#510).
|
|
1702
|
+
*
|
|
1703
|
+
* Pass `salt` + `credentialId` to `PasskeyPrfSecretProvider.unlock(...)` to obtain the
|
|
1704
|
+
* `passkeySecret` that {@link RecoverParams} takes. They arrive HERE, on the challenge, because the
|
|
1705
|
+
* salt is an INPUT to the authenticator ceremony while the wraps themselves are released only after
|
|
1706
|
+
* every factor passes — so a device that lost its local storage (which is the recovery case) would
|
|
1707
|
+
* otherwise have no way to use its passkey at all.
|
|
1708
|
+
*
|
|
1709
|
+
* Neither value is a secret: the credential id is public by WebAuthn design, and the salt is
|
|
1710
|
+
* meaningless without the authenticator and a user-verified ceremony.
|
|
1711
|
+
*/
|
|
1712
|
+
passkeyWrap?: {
|
|
1713
|
+
salt: string;
|
|
1714
|
+
credentialId: string;
|
|
1715
|
+
};
|
|
1682
1716
|
}
|
|
1683
1717
|
/**
|
|
1684
1718
|
* How the key that opens a backup was wrapped (backend `RecoveryKeyWrap`, #510).
|
|
@@ -1737,6 +1771,23 @@ interface RegisterRecoveryParams {
|
|
|
1737
1771
|
salt: string;
|
|
1738
1772
|
credentialId: string;
|
|
1739
1773
|
};
|
|
1774
|
+
/**
|
|
1775
|
+
* Enrol the passkey as a recovery FACTOR **in place of** the recovery code (#511).
|
|
1776
|
+
*
|
|
1777
|
+
* The wrap (`passkey` above) decides what can OPEN the backup; a factor decides what the server
|
|
1778
|
+
* demands before releasing it. Enrolling the passkey as a factor is the difference between "the
|
|
1779
|
+
* code is still mandatory" and a user who never has to write one down.
|
|
1780
|
+
*
|
|
1781
|
+
* It REPLACES `recovery_code` rather than adding to it, because the gate is an AND of every enrolled
|
|
1782
|
+
* factor: enrolling both would mean losing EITHER the code or the passkey makes the wallet
|
|
1783
|
+
* unrecoverable — strictly worse than either alone.
|
|
1784
|
+
*
|
|
1785
|
+
* Requires `passkey`, and that credential must ALREADY be registered with the platform (the API
|
|
1786
|
+
* refuses an unknown credential id — a factor nobody can satisfy is a lock-out discovered at the
|
|
1787
|
+
* worst possible moment). Defaults to false: trading a written code for a synced authenticator is
|
|
1788
|
+
* the app's call, not the SDK's.
|
|
1789
|
+
*/
|
|
1790
|
+
passkeyFactor?: boolean;
|
|
1740
1791
|
}
|
|
1741
1792
|
/** Parameters for `recovery.recover(...)` / `recovery.retrieveShare(...)`. */
|
|
1742
1793
|
interface RecoverParams {
|
package/dist/index.js
CHANGED
|
@@ -484,6 +484,54 @@ var Onramp = class {
|
|
|
484
484
|
}
|
|
485
485
|
};
|
|
486
486
|
|
|
487
|
+
// src/passkey/assertion.ts
|
|
488
|
+
function isPasskeyAssertionSupported() {
|
|
489
|
+
return typeof globalThis.navigator !== "undefined" && typeof globalThis.navigator.credentials !== "undefined";
|
|
490
|
+
}
|
|
491
|
+
async function defaultAssertionCeremony() {
|
|
492
|
+
let mod;
|
|
493
|
+
try {
|
|
494
|
+
mod = await import('@simplewebauthn/browser');
|
|
495
|
+
} catch {
|
|
496
|
+
throw new WaaskeyError('Passkey step-up requires "@simplewebauthn/browser" installed, or pass a custom ceremony.', "unsupported");
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
get: (options) => mod.startAuthentication({ optionsJSON: options })
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
async function getSigningAssertion(challenge, options = {}) {
|
|
503
|
+
if (options.ceremony === void 0 && !isPasskeyAssertionSupported()) {
|
|
504
|
+
throw new WaaskeyError("Passkey step-up is not available in this runtime (no WebAuthn API).", "unsupported");
|
|
505
|
+
}
|
|
506
|
+
const ceremony = options.ceremony ?? await defaultAssertionCeremony();
|
|
507
|
+
const rpId = options.rpId ?? (typeof location === "undefined" ? void 0 : location.hostname);
|
|
508
|
+
const requestOptions = {
|
|
509
|
+
challenge,
|
|
510
|
+
userVerification: "required",
|
|
511
|
+
...rpId ? { rpId } : {},
|
|
512
|
+
...options.credentialId ? { allowCredentials: [{ id: options.credentialId, type: "public-key" }] } : {}
|
|
513
|
+
};
|
|
514
|
+
try {
|
|
515
|
+
return await ceremony.get(requestOptions);
|
|
516
|
+
} catch (cause) {
|
|
517
|
+
if (cause instanceof WaaskeyError) throw cause;
|
|
518
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
519
|
+
if (/cancel|abort|not allowed|user gesture/i.test(msg)) {
|
|
520
|
+
throw new WaaskeyError("Passkey authentication was cancelled by the user.", "aborted", { cause });
|
|
521
|
+
}
|
|
522
|
+
throw new WaaskeyError("Passkey step-up assertion failed.", "unsupported", { cause });
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/passkey/recovery-factor.ts
|
|
527
|
+
function passkeyFactorEnrollment(credentialId) {
|
|
528
|
+
return { type: "passkey", credential: credentialId };
|
|
529
|
+
}
|
|
530
|
+
async function passkeyFactorVerification(challenge, options = {}) {
|
|
531
|
+
const assertion = await getSigningAssertion(challenge, options);
|
|
532
|
+
return { type: "passkey", token: JSON.stringify(assertion) };
|
|
533
|
+
}
|
|
534
|
+
|
|
487
535
|
// src/storage/crypto.ts
|
|
488
536
|
var PBKDF2_ITERATIONS = 21e4;
|
|
489
537
|
var PBKDF2_HASH = "SHA-256";
|
|
@@ -685,8 +733,12 @@ var Recovery = class {
|
|
|
685
733
|
async function buildRecoveryRegistration(params) {
|
|
686
734
|
const recoveryCode = params.recoveryCode ?? generateRecoveryCode();
|
|
687
735
|
const backup = params.passkey ? await sealRecoveryBackup(params.share, recoveryCode, params.passkey) : { ciphertext: await sealWithPassword(recoveryCode, params.share), keyWraps: void 0 };
|
|
736
|
+
if (params.passkeyFactor && !params.passkey) {
|
|
737
|
+
throw new WaaskeyError("passkeyFactor requires `passkey` \u2014 a factor for a credential this backup cannot open would be a lock-out.", "validation");
|
|
738
|
+
}
|
|
739
|
+
const strongFactor = params.passkeyFactor ? passkeyFactorEnrollment(params.passkey.credentialId) : { type: "recovery_code", credential: await sha256Hex(recoveryCode) };
|
|
688
740
|
const factors = [
|
|
689
|
-
|
|
741
|
+
strongFactor,
|
|
690
742
|
{ type: "totp", credential: params.totpSecret },
|
|
691
743
|
{ type: "email_otp", credential: params.email },
|
|
692
744
|
...params.extraFactors ?? []
|
|
@@ -1169,45 +1221,6 @@ function deserializeShare(blob) {
|
|
|
1169
1221
|
};
|
|
1170
1222
|
}
|
|
1171
1223
|
|
|
1172
|
-
// src/passkey/assertion.ts
|
|
1173
|
-
function isPasskeyAssertionSupported() {
|
|
1174
|
-
return typeof globalThis.navigator !== "undefined" && typeof globalThis.navigator.credentials !== "undefined";
|
|
1175
|
-
}
|
|
1176
|
-
async function defaultAssertionCeremony() {
|
|
1177
|
-
let mod;
|
|
1178
|
-
try {
|
|
1179
|
-
mod = await import('@simplewebauthn/browser');
|
|
1180
|
-
} catch {
|
|
1181
|
-
throw new WaaskeyError('Passkey step-up requires "@simplewebauthn/browser" installed, or pass a custom ceremony.', "unsupported");
|
|
1182
|
-
}
|
|
1183
|
-
return {
|
|
1184
|
-
get: (options) => mod.startAuthentication({ optionsJSON: options })
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
async function getSigningAssertion(challenge, options = {}) {
|
|
1188
|
-
if (options.ceremony === void 0 && !isPasskeyAssertionSupported()) {
|
|
1189
|
-
throw new WaaskeyError("Passkey step-up is not available in this runtime (no WebAuthn API).", "unsupported");
|
|
1190
|
-
}
|
|
1191
|
-
const ceremony = options.ceremony ?? await defaultAssertionCeremony();
|
|
1192
|
-
const rpId = options.rpId ?? (typeof location === "undefined" ? void 0 : location.hostname);
|
|
1193
|
-
const requestOptions = {
|
|
1194
|
-
challenge,
|
|
1195
|
-
userVerification: "required",
|
|
1196
|
-
...rpId ? { rpId } : {},
|
|
1197
|
-
...options.credentialId ? { allowCredentials: [{ id: options.credentialId, type: "public-key" }] } : {}
|
|
1198
|
-
};
|
|
1199
|
-
try {
|
|
1200
|
-
return await ceremony.get(requestOptions);
|
|
1201
|
-
} catch (cause) {
|
|
1202
|
-
if (cause instanceof WaaskeyError) throw cause;
|
|
1203
|
-
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
1204
|
-
if (/cancel|abort|not allowed|user gesture/i.test(msg)) {
|
|
1205
|
-
throw new WaaskeyError("Passkey authentication was cancelled by the user.", "aborted", { cause });
|
|
1206
|
-
}
|
|
1207
|
-
throw new WaaskeyError("Passkey step-up assertion failed.", "unsupported", { cause });
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
1224
|
// src/wallet.ts
|
|
1212
1225
|
var DEVICE_ROLE = "device";
|
|
1213
1226
|
var Wallet = class {
|
|
@@ -2359,15 +2372,6 @@ function resolveSink(analytics, http) {
|
|
|
2359
2372
|
return analytics ?? new HttpAnalyticsSink(http);
|
|
2360
2373
|
}
|
|
2361
2374
|
|
|
2362
|
-
// src/passkey/recovery-factor.ts
|
|
2363
|
-
function passkeyFactorEnrollment(credentialId) {
|
|
2364
|
-
return { type: "passkey", credential: credentialId };
|
|
2365
|
-
}
|
|
2366
|
-
async function passkeyFactorVerification(challenge, options = {}) {
|
|
2367
|
-
const assertion = await getSigningAssertion(challenge, options);
|
|
2368
|
-
return { type: "passkey", token: JSON.stringify(assertion) };
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
2375
|
// src/mpc/wasm-core.ts
|
|
2372
2376
|
var WasmMpcCore = class {
|
|
2373
2377
|
constructor(load) {
|