@calimero-network/mero-js 29.7.6 → 29.7.7

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.
@@ -408,6 +408,13 @@ export interface ServedOrigins {
408
408
  */
409
409
  export interface SignInOptions {
410
410
  origin?: string | null;
411
+ /**
412
+ * Closes an open passkey prompt when it aborts (a Cancel button): the
413
+ * ceremony then rejects as a dismissed prompt does (`NotAllowedError`; in a
414
+ * sign-up's confirmation, {@link ConfirmCancelled}). Given to the
415
+ * {@link PasskeyProvider} as `signal`.
416
+ */
417
+ signal?: AbortSignal;
411
418
  }
412
419
  /**
413
420
  * {@link SignInOptions} for a sign-up, and the invite it is made with: an
@@ -648,9 +655,21 @@ export declare class CalimeroClient {
648
655
  */
649
656
  refresh(): Promise<CalimeroSession>;
650
657
  private exchangeRefresh;
658
+ /**
659
+ * End this session. The client is signed out whether or not the node
660
+ * answers: the revoke (`POST /auth/logout`) is attempted, and its failure
661
+ * (the node unreachable, say) is thrown after the session is dropped, so a
662
+ * caller can say the server may still hold it, but no request after this
663
+ * one carries its token.
664
+ */
651
665
  signOut(): Promise<void>;
652
- /** End every session of the signed-in account, on every device, this one too. */
666
+ /**
667
+ * End every session of the signed-in account, on every device, this one
668
+ * too. Like {@link signOut}, this client is signed out even when the
669
+ * request fails, and the failure is thrown.
670
+ */
653
671
  signOutEverywhere(): Promise<void>;
672
+ private signingOut;
654
673
  /** Who the credential is: the account, how it signed in, and the package (`app`) and `origin` a session is bound to. */
655
674
  me(): Promise<MeInfo>;
656
675
  /**
@@ -511,13 +511,14 @@ export class CalimeroClient {
511
511
  const origin = this.pageOrigin(options);
512
512
  const invite = options.invite ? { invite: options.invite } : {};
513
513
  const begin = await this.begin('auth/passkey/register/begin', origin === undefined ? { name, ...invite } : { name, origin, ...invite }, origin);
514
- const registration = await passkey.create(begin.options);
514
+ const signal = options.signal;
515
+ const registration = await (signal ? passkey.create(begin.options, { signal }) : passkey.create(begin.options));
515
516
  // The node checks the sign-up's confirmation with the user present, not
516
517
  // verified (core `api.rs`: `rp(node)`, `require_uv: false`), as it made
517
518
  // the passkey with `preferred`: asking `required` here would refuse a
518
519
  // security key without a PIN after it has made the passkey, leaving a
519
520
  // passkey with no account.
520
- const session = await this.confirmed('POST', 'auth/passkey/register/finish', { challengeId: begin.challengeId, registration, ...invite }, passkey, undefined, 'preferred');
521
+ const session = await this.confirmed('POST', 'auth/passkey/register/finish', { challengeId: begin.challengeId, registration, ...invite }, passkey, undefined, 'preferred', signal);
521
522
  this.useSession(session);
522
523
  return session;
523
524
  }
@@ -537,7 +538,10 @@ export class CalimeroClient {
537
538
  const origin = this.pageOrigin(options);
538
539
  const begin = await this.begin('auth/passkey/login/begin', origin === undefined ? undefined : { origin }, origin);
539
540
  const rpId = begin.options.rpId ?? begin.options.rp?.id ?? '';
540
- const assertion = await passkey.get(fromB64url(begin.options.challenge), rpId);
541
+ const challenge = fromB64url(begin.options.challenge);
542
+ const assertion = await (options.signal
543
+ ? passkey.get(challenge, rpId, undefined, { signal: options.signal })
544
+ : passkey.get(challenge, rpId));
541
545
  const session = await this.post('auth/passkey/login/finish', {
542
546
  challengeId: begin.challengeId,
543
547
  assertion,
@@ -617,16 +621,32 @@ export class CalimeroClient {
617
621
  this.useSession(session);
618
622
  return session;
619
623
  }
624
+ /**
625
+ * End this session. The client is signed out whether or not the node
626
+ * answers: the revoke (`POST /auth/logout`) is attempted, and its failure
627
+ * (the node unreachable, say) is thrown after the session is dropped, so a
628
+ * caller can say the server may still hold it, but no request after this
629
+ * one carries its token.
630
+ */
620
631
  async signOut() {
621
- await this.post('auth/logout');
622
- this.auth = undefined;
623
- this.current = undefined;
632
+ await this.signingOut('auth/logout');
624
633
  }
625
- /** End every session of the signed-in account, on every device, this one too. */
634
+ /**
635
+ * End every session of the signed-in account, on every device, this one
636
+ * too. Like {@link signOut}, this client is signed out even when the
637
+ * request fails, and the failure is thrown.
638
+ */
626
639
  async signOutEverywhere() {
627
- await this.post('auth/logout-all');
628
- this.auth = undefined;
629
- this.current = undefined;
640
+ await this.signingOut('auth/logout-all');
641
+ }
642
+ async signingOut(path) {
643
+ try {
644
+ await this.post(path);
645
+ }
646
+ finally {
647
+ this.auth = undefined;
648
+ this.current = undefined;
649
+ }
630
650
  }
631
651
  /** Who the credential is: the account, how it signed in, and the package (`app`) and `origin` a session is bound to. */
632
652
  me() {
@@ -915,7 +935,7 @@ export class CalimeroClient {
915
935
  deleteConfirmed(path, passkey, body = {}) {
916
936
  return this.confirmed('DELETE', path, body, passkey);
917
937
  }
918
- async confirmed(method, path, body, passkey, send = (b) => this.json(method, path, b), userVerification = 'required') {
938
+ async confirmed(method, path, body, passkey, send = (b) => this.json(method, path, b), userVerification = 'required', signal) {
919
939
  try {
920
940
  return await send(body);
921
941
  }
@@ -931,6 +951,7 @@ export class CalimeroClient {
931
951
  // requirement the node names itself wins.
932
952
  assertion = await passkey.get(fromB64url(d.challenge), d.rpId, d.allowCredentials, {
933
953
  userVerification: userVerificationOf(d.userVerification, userVerification),
954
+ ...(signal ? { signal } : {}),
934
955
  });
935
956
  }
936
957
  catch (declined) {
@@ -8,7 +8,7 @@ export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER }
8
8
  export type { AttestationDoc, CloudPolicy, CloudReleasePolicy } from './attestation.js';
9
9
  export { browserPasskey, b64url, fromB64url } from './passkey.js';
10
10
  export { MAX_RECOVERY_NONCE_BYTES, RECOVERY_DOMAIN, newRecoverySeed, parseRecoverySeed, recoveryMessage, recoveryPublicKey, recoverySeedText, signRecovery, } from './recovery.js';
11
- export type { AssertionJSON, PasskeyGetOptions, PasskeyProvider, PasskeyUserVerification, RegistrationJSON, } from './passkey.js';
11
+ export type { AssertionJSON, PasskeyCreateOptions, PasskeyGetOptions, PasskeyProvider, PasskeyUserVerification, RegistrationJSON, } from './passkey.js';
12
12
  export { MAX_REPORT_PAGE, RELEASE_VERIFIER, compareVersions, confirmationCode, consentReturn, pageQuery, pendingApprovalsFrom, publisherBindingBytes, publisherBindingMessage, releaseConsent, releaseNotesUrl, stalledBy, upgradeConsent, verifyReleaseUrl, withdrawConsent, } from './approvals.js';
13
13
  export type { ActorKind, AppsReport, Authority, ClusterRelease, ConsentReleaseModel, ConsentRequest, ConsentResult, ConsentScope, ConsentStarted, ConsentStatus, OpenConsentOptions, Governance, LastUpgrade, OpsAppsReport, OpsOrgsReport, PageOptions, PendingApprovals, PendingRelease, PendingUpgrade, PublisherBindingInfo, PublisherProof, ReleaseDeadline, ReleaseRules, ReportScope, ReportedApp, ReportedBundle, ReportedRelease, ReportedReplica, UpgradeAuthority, } from './approvals.js';
14
14
  export { DEFAULT_ORG_CAPABILITIES, ORG_CAPABILITIES, orgCapabilities } from './capabilities.js';
@@ -26,16 +26,32 @@ export interface PasskeyGetOptions {
26
26
  * `"preferred"` is enough.
27
27
  */
28
28
  userVerification?: PasskeyUserVerification;
29
+ /**
30
+ * Closes the open prompt when it aborts (a Cancel button, a page leaving).
31
+ * The browser provider then rejects as a dismissed prompt does
32
+ * (`NotAllowedError`), so the caller treats it as a cancel.
33
+ */
34
+ signal?: AbortSignal;
35
+ }
36
+ /** What a passkey creation takes beyond the node's options. */
37
+ export interface PasskeyCreateOptions {
38
+ /** As {@link PasskeyGetOptions.signal}: aborting closes the prompt, rejecting as a cancel (`NotAllowedError`). */
39
+ signal?: AbortSignal;
29
40
  }
30
41
  /** Something that holds a passkey. */
31
42
  export interface PasskeyProvider {
32
- /** Answer `navigator.credentials.create` for the node's options. */
33
- create(options: Record<string, unknown>): Promise<RegistrationJSON>;
43
+ /**
44
+ * Answer `navigator.credentials.create` for the node's options.
45
+ * `createOptions.signal` closes the prompt when it aborts; a provider with
46
+ * no prompt to close may ignore it.
47
+ */
48
+ create(options: Record<string, unknown>, createOptions?: PasskeyCreateOptions): Promise<RegistrationJSON>;
34
49
  /**
35
50
  * Sign `challenge` (an assertion), optionally limited to
36
51
  * `allowCredentials`. `options.userVerification` is `"required"` when the
37
52
  * client answers a step-up confirmation (`"preferred"` for the sign-up's);
38
53
  * a provider that always verifies the user may ignore it.
54
+ * `options.signal` closes the prompt when it aborts.
39
55
  */
40
56
  get(challenge: Uint8Array, rpId: string, allowCredentials?: string[], options?: PasskeyGetOptions): Promise<AssertionJSON>;
41
57
  }
@@ -25,11 +25,22 @@ function cancelled(message) {
25
25
  error.name = "NotAllowedError";
26
26
  return error;
27
27
  }
28
- async function ask(method, options) {
28
+ async function ask(method, options, signal, what) {
29
29
  const container = navigator.credentials;
30
30
  const own = globalThis.CredentialsContainer?.prototype?.[method];
31
31
  const call = own ?? container[method];
32
- return call.call(container, options);
32
+ try {
33
+ return await call.call(container, signal ? { ...options, signal } : options);
34
+ }
35
+ catch (error) {
36
+ // An aborted `signal` rejects with its reason (an `AbortError` unless the
37
+ // caller gave another): the prompt was closed on purpose, so it reads as
38
+ // a dismissed one.
39
+ if (signal?.aborted || error?.name === "AbortError") {
40
+ throw Object.assign(cancelled(`${what} was cancelled`), { cause: error });
41
+ }
42
+ throw error;
43
+ }
33
44
  }
34
45
  /**
35
46
  * The node's algorithms with ES256 alone when it is among them. Some password
@@ -48,7 +59,7 @@ export function es256First(params) {
48
59
  /** The platform authenticator (Touch ID, Face ID, Windows Hello, a security key, a synced passkey). */
49
60
  export function browserPasskey() {
50
61
  return {
51
- async create(options) {
62
+ async create(options, createOptions) {
52
63
  const o = options;
53
64
  const cred = (await ask("create", {
54
65
  publicKey: {
@@ -64,7 +75,7 @@ export function browserPasskey() {
64
75
  attestation: "none",
65
76
  timeout: o.timeout,
66
77
  },
67
- }));
78
+ }, createOptions?.signal, "passkey creation"));
68
79
  if (!cred)
69
80
  throw cancelled("passkey creation was cancelled");
70
81
  const r = cred.response;
@@ -84,7 +95,7 @@ export function browserPasskey() {
84
95
  id: buf(fromB64url(id)),
85
96
  })),
86
97
  },
87
- }));
98
+ }, options?.signal, "passkey sign-in"));
88
99
  if (!cred)
89
100
  throw cancelled("passkey sign-in was cancelled");
90
101
  const r = cred.response;