@calimero-network/mero-js 29.7.3 → 29.7.4

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/README.md CHANGED
@@ -110,7 +110,7 @@ Pinned measurements go through `policy.verifyQuote` (`createQuoteVerifier`).
110
110
  - **Operator** (ops token): `opsApps`, `opsOrgs`, `opsServedOrigins` (every custom domain the log holds, by package label), `setReleaseDeadline`, `liftReleaseDeadline`.
111
111
  - **Any route**: `get`, `post`, `put`, `delete`; `postConfirmed` and `deleteConfirmed` answer a `412 CONFIRM_REQUIRED` with a passkey.
112
112
 
113
- Refusals are typed (`LoginRequired`, `PasskeyUnknownHere`, `WrongNode`, `NotFound`, `ConfirmRequired`, `ConfirmCancelled`,
113
+ Refusals are typed (`LoginRequired`, `PasskeyUnknownHere`, `WrongNode`, `NotFound`, `ConfirmRequired`, `ConfirmCancelled`, `ConfirmPromptBlocked`,
114
114
  `ArgsTooLarge`, `IdempotencyMismatch`, `ReleaseDeadlineError`, `ConsentStale`, `AppError`, `TrapError`,
115
115
  `UnavailableError`, `OriginNotRegistered`, `BadOrigin`, `OriginTaken`, `QuotaExceeded`),
116
116
  all extending `CalimeroError` with the node's `status` and `code`. A `401` once
@@ -12,7 +12,7 @@
12
12
  import { type AttestationDoc, type CloudPolicy } from './attestation.js';
13
13
  import { type PasskeyProvider } from './passkey.js';
14
14
  import { type AppsReport, type ConsentRequest, type ConsentResult, type ConsentStarted, type OpenConsentOptions, type OpsAppsReport, type OpsOrgsReport, type PageOptions, type PendingApprovals, type PublisherBindingInfo, type PublisherProof, type ReleaseRules } from './approvals.js';
15
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
15
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
16
16
  export type { Committed, SignUpRefusal } from './errors.js';
17
17
  export interface CalimeroSession {
18
18
  accountId: string;
@@ -12,11 +12,11 @@
12
12
  import { SealedTransportError, createSealedFetch } from '../sealed/sealed.js';
13
13
  import { fetchAttestedKey } from './attestation.js';
14
14
  import { DEFAULT_DIRECTORY, orderNodes, readDirectory } from './directory.js';
15
- import { CalimeroError, ConfirmCancelled, ConfirmRequired, LoginRequired, OriginNotRegistered, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
15
+ import { CalimeroError, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, LoginRequired, OriginNotRegistered, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
16
16
  import { b64url, fromB64url } from './passkey.js';
17
17
  import { signRecovery } from './recovery.js';
18
18
  import { confirmationCode, pageQuery, pendingApprovalsFrom, } from './approvals.js';
19
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
19
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
20
20
  /** A deployer's key, as `GET /deployers` lists it and `DELETE /deployers/{key}` takes it. */
21
21
  export function deployerKey(deployer) {
22
22
  return 'account' in deployer ? `account:${deployer.account}` : `org:${deployer.org}`;
@@ -55,6 +55,33 @@ export function pageDeployment(href) {
55
55
  return null;
56
56
  }
57
57
  }
58
+ /**
59
+ * The user verification a confirmation asks the passkey for: what the node's
60
+ * `412` details name, if it names one WebAuthn knows, else `required` (the
61
+ * node takes a confirmation only with the user verified).
62
+ */
63
+ function userVerificationOf(named) {
64
+ return named === 'required' || named === 'preferred' || named === 'discouraged' ? named : 'required';
65
+ }
66
+ /**
67
+ * What a failed confirmation prompt means to the caller. The browser or a
68
+ * password manager refusing to show the prompt (the page not focused because
69
+ * an extension's popup still holds focus, or another prompt still open) is
70
+ * {@link ConfirmPromptBlocked}; the user dismissing it (`NotAllowedError`, or
71
+ * `AbortError` when it was aborted) is {@link ConfirmCancelled}; anything
72
+ * else is a real failure, passed through.
73
+ */
74
+ function promptFailure(error) {
75
+ const e = error;
76
+ const name = e?.name;
77
+ const message = typeof e?.message === 'string' ? e.message : '';
78
+ if (name === 'InvalidStateError' || /not focused|already pending/i.test(message)) {
79
+ return new ConfirmPromptBlocked(error);
80
+ }
81
+ if (name === 'NotAllowedError' || name === 'AbortError')
82
+ return new ConfirmCancelled(error);
83
+ return error;
84
+ }
58
85
  /** One URL path segment, so an id cannot reach another route. */
59
86
  function seg(value) {
60
87
  return encodeURIComponent(value);
@@ -891,15 +918,15 @@ export class CalimeroClient {
891
918
  const d = error.details;
892
919
  let assertion;
893
920
  try {
894
- assertion = await passkey.get(fromB64url(d.challenge), d.rpId, d.allowCredentials);
921
+ // The node takes a confirmation only with the user verified (PIN or
922
+ // biometric); `preferred` lets a password manager skip it, and the
923
+ // node then refuses. A requirement the node names itself wins.
924
+ assertion = await passkey.get(fromB64url(d.challenge), d.rpId, d.allowCredentials, {
925
+ userVerification: userVerificationOf(d.userVerification),
926
+ });
895
927
  }
896
928
  catch (declined) {
897
- // WebAuthn rejects a prompt the user dismissed with `NotAllowedError`
898
- // (`AbortError` when it was aborted); anything else is a real failure.
899
- const name = declined?.name;
900
- if (name === 'NotAllowedError' || name === 'AbortError')
901
- throw new ConfirmCancelled(declined);
902
- throw declined;
929
+ throw promptFailure(declined);
903
930
  }
904
931
  return send({ ...body, confirm: { challengeId: d.challengeId, assertion } });
905
932
  }
@@ -86,6 +86,18 @@ export declare class ConfirmCancelled extends Error {
86
86
  readonly cause?: unknown;
87
87
  constructor(cause?: unknown);
88
88
  }
89
+ /**
90
+ * The browser or a password manager refused to show the passkey prompt a
91
+ * confirmation asked for: the page was not focused (an extension's popup
92
+ * still held focus), or another prompt was still open. The user did not
93
+ * decline; nothing was sent. Ask them to try again from a click, e.g. "Your
94
+ * password manager or browser blocked the second prompt. Click to try again."
95
+ * `cause` is the browser's error.
96
+ */
97
+ export declare class ConfirmPromptBlocked extends Error {
98
+ readonly cause?: unknown;
99
+ constructor(cause?: unknown);
100
+ }
89
101
  /** `413 ARGS_TOO_LARGE`: the call's arguments are over the node's limit; it did not run. */
90
102
  export declare class ArgsTooLarge extends CalimeroError {
91
103
  constructor(message: string, details?: Record<string, unknown>);
@@ -107,6 +107,21 @@ export class ConfirmCancelled extends Error {
107
107
  this.name = 'ConfirmCancelled';
108
108
  }
109
109
  }
110
+ /**
111
+ * The browser or a password manager refused to show the passkey prompt a
112
+ * confirmation asked for: the page was not focused (an extension's popup
113
+ * still held focus), or another prompt was still open. The user did not
114
+ * decline; nothing was sent. Ask them to try again from a click, e.g. "Your
115
+ * password manager or browser blocked the second prompt. Click to try again."
116
+ * `cause` is the browser's error.
117
+ */
118
+ export class ConfirmPromptBlocked extends Error {
119
+ constructor(cause) {
120
+ super('the passkey prompt was blocked by the browser or a password manager');
121
+ this.cause = cause;
122
+ this.name = 'ConfirmPromptBlocked';
123
+ }
124
+ }
110
125
  /** `413 ARGS_TOO_LARGE`: the call's arguments are over the node's limit; it did not run. */
111
126
  export class ArgsTooLarge extends CalimeroError {
112
127
  constructor(message, details = {}) {
@@ -1,14 +1,14 @@
1
1
  export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
2
2
  export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
3
3
  export type { DirectoryFailure } from './directory.js';
4
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
4
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
5
5
  export type { Committed, SignUpRefusal } from './errors.js';
6
6
  export type { AppInfo, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, MeInfo, PackageOrigin, PackageOriginAdded, PackageOriginRemoved, PackageOrigins, ServedOrigin, ServedOrigins, SignInOptions, AccountRecovery, SignUpOptions, SignUpPolicy, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgRecovery, RecoveryTimes, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
7
7
  export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
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, PasskeyProvider, RegistrationJSON } from './passkey.js';
11
+ export type { AssertionJSON, 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';
@@ -1,6 +1,6 @@
1
1
  export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
2
2
  export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
3
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
3
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
4
4
  export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
5
5
  export { browserPasskey, b64url, fromB64url } from './passkey.js';
6
6
  export { MAX_RECOVERY_NONCE_BYTES, RECOVERY_DOMAIN, newRecoverySeed, parseRecoverySeed, recoveryMessage, recoveryPublicKey, recoverySeedText, signRecovery, } from './recovery.js';
@@ -14,12 +14,28 @@ export interface AssertionJSON {
14
14
  clientDataJson: string;
15
15
  signature: string;
16
16
  }
17
+ /** How strongly an assertion must verify the user (WebAuthn's `UserVerificationRequirement`). */
18
+ export type PasskeyUserVerification = "required" | "preferred" | "discouraged";
19
+ /** What a confirmation asks of the passkey beyond the challenge. */
20
+ export interface PasskeyGetOptions {
21
+ /**
22
+ * `"required"` for a confirmation (`412 CONFIRM_REQUIRED`): the node
23
+ * accepts only an assertion with the user-verified flag (PIN or
24
+ * biometric). Absent for a plain sign-in, where `"preferred"` is enough.
25
+ */
26
+ userVerification?: PasskeyUserVerification;
27
+ }
17
28
  /** Something that holds a passkey. */
18
29
  export interface PasskeyProvider {
19
30
  /** Answer `navigator.credentials.create` for the node's options. */
20
31
  create(options: Record<string, unknown>): Promise<RegistrationJSON>;
21
- /** Sign `challenge` (an assertion), optionally limited to `allowCredentials`. */
22
- get(challenge: Uint8Array, rpId: string, allowCredentials?: string[]): Promise<AssertionJSON>;
32
+ /**
33
+ * Sign `challenge` (an assertion), optionally limited to
34
+ * `allowCredentials`. `options.userVerification` is `"required"` when the
35
+ * client answers a confirmation; a provider that always verifies the user
36
+ * may ignore it.
37
+ */
38
+ get(challenge: Uint8Array, rpId: string, allowCredentials?: string[], options?: PasskeyGetOptions): Promise<AssertionJSON>;
23
39
  }
24
40
  export declare function b64url(bytes: Uint8Array): string;
25
41
  export declare function fromB64url(s: string): Uint8Array;
@@ -70,12 +70,12 @@ export function browserPasskey() {
70
70
  clientDataJson: b64url(new Uint8Array(r.clientDataJSON)),
71
71
  };
72
72
  },
73
- async get(challenge, rpId, allowCredentials) {
73
+ async get(challenge, rpId, allowCredentials, options) {
74
74
  const cred = (await ask("get", {
75
75
  publicKey: {
76
76
  challenge: buf(challenge),
77
77
  rpId,
78
- userVerification: "preferred",
78
+ userVerification: options?.userVerification ?? "preferred",
79
79
  allowCredentials: (allowCredentials ?? []).map((id) => ({
80
80
  type: "public-key",
81
81
  id: buf(fromB64url(id)),