@le-space/orbitdb-identity-provider-webauthn-did 0.9.0 → 0.9.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changes
2
2
 
3
+ ## 0.9.1
4
+
5
+ `createCredential` honours the choice of authenticator that its options, its types and
6
+ `docs/API.md` have offered all along.
7
+
8
+ ### Fixed
9
+
10
+ - `authenticatorType` (`'platform'`, `'cross-platform'` or `'any'`) and its WebAuthn name
11
+ `authenticatorAttachment` reach the browser. Since 0.2.8 neither did: `buildAuthenticatorSelection`
12
+ dropped them, so the request named no attachment and the browser offered every kind of
13
+ authenticator, a security key as much as the device's own — though the code asked for
14
+ `'platform'`. That stays the default, so nothing changes for a caller that passes neither.
15
+ - An unknown value, or the two disagreeing, is a `TypeError` before anyone is asked, instead of
16
+ being ignored.
17
+
18
+ Tested in Chromium with a virtual authenticator on the USB transport: offered by default, asked for
19
+ with `'cross-platform'`, never asked with `'platform'`. The vault test from 0.9.0 now also runs with
20
+ two such security keys.
21
+
3
22
  ## 0.9.0
4
23
 
5
24
  What applications kept writing for themselves, once and here: one read of the PRF output for every
package/README.md CHANGED
@@ -19,6 +19,7 @@ This package provides:
19
19
  - Authentication/assertion requests omit `allowCredentials` by default, so the browser/authenticator can resolve the credential discoverably.
20
20
  - You can switch this centrally with `configureWebAuthn({ discoverableCredentials: true|false })`.
21
21
  - Registration is still the point where this package extracts the credential public key from attestation.
22
+ - Registration offers every kind of authenticator — the device's own, a security key such as a YubiKey, a phone — unless `createCredential({ authenticatorType: 'platform' | 'cross-platform' })` narrows it (honoured since 0.9.1).
22
23
  - Later `navigator.credentials.get()` assertions do not reliably return the public key again, so identity reconstruction still needs metadata from somewhere else.
23
24
 
24
25
  Since **0.6.0** the passkey alone is enough, and stored metadata is a convenience rather than a requirement:
package/docs/API.md CHANGED
@@ -61,7 +61,17 @@ Options:
61
61
  - `encryptKeystore?: boolean`
62
62
  - `keystoreEncryptionMethod?: 'prf' | 'largeBlob' | 'hmac-secret'`
63
63
  - `discoverableCredentials?: boolean`
64
- - `authenticatorType?: 'platform' | 'cross-platform' | 'any'`
64
+ - `authenticatorType?: 'platform' | 'cross-platform' | 'any'` — which
65
+ authenticators the browser offers: the device's own (Touch ID, Windows
66
+ Hello, a phone's screen lock), a security key or another phone, or every
67
+ kind. `'any'` is the default and names none in the request.
68
+ - `authenticatorAttachment?: 'platform' | 'cross-platform'` — the WebAuthn name
69
+ for the same choice. Giving both with different values, or a value neither
70
+ knows, throws a `TypeError` before anyone is asked.
71
+
72
+ Before 0.9.1 both were listed here and neither reached the browser: the request
73
+ named no attachment, so every kind of authenticator was offered — a YubiKey as
74
+ much as the device's own. That is still the default.
65
75
 
66
76
  ```js
67
77
  const credential = await WebAuthnDIDProvider.createCredential({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/orbitdb-identity-provider-webauthn-did",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "WebAuthn-based DID identity provider for OrbitDB for hardware-secured wallets and biometric Passkey authentication",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -44,6 +44,65 @@ export function resolveWebAuthnConfig(options = {}) {
44
44
  };
45
45
  }
46
46
 
47
+ const AUTHENTICATOR_TYPES = ['platform', 'cross-platform', 'any'];
48
+ const AUTHENTICATOR_ATTACHMENTS = ['platform', 'cross-platform'];
49
+
50
+ /**
51
+ * The attachment a registration asks for: `authenticatorType` —
52
+ * `'platform'` (the device's own authenticator), `'cross-platform'` (a
53
+ * security key or a phone) or `'any'` — or its WebAuthn name
54
+ * `authenticatorAttachment`, which has no word for "any".
55
+ *
56
+ * A value this does not know is refused rather than ignored: a typo would
57
+ * otherwise quietly offer every authenticator, or none the caller meant.
58
+ *
59
+ * @param {Object} [options]
60
+ * @param {'platform'|'cross-platform'|'any'} [options.authenticatorType]
61
+ * @param {'platform'|'cross-platform'} [options.authenticatorAttachment]
62
+ * @param {'platform'|'cross-platform'|'any'} [fallback='any'] - When neither
63
+ * is given.
64
+ * @returns {'platform'|'cross-platform'|undefined} `undefined` for any
65
+ * authenticator: the request then names none.
66
+ */
67
+ export function resolveAuthenticatorAttachment(
68
+ { authenticatorType, authenticatorAttachment } = {},
69
+ fallback = 'any'
70
+ ) {
71
+ if (
72
+ authenticatorType != null &&
73
+ !AUTHENTICATOR_TYPES.includes(authenticatorType)
74
+ ) {
75
+ throw new TypeError(
76
+ `authenticatorType must be one of ${AUTHENTICATOR_TYPES.join(', ')}, not '${authenticatorType}'`
77
+ );
78
+ }
79
+ if (
80
+ authenticatorAttachment != null &&
81
+ !AUTHENTICATOR_ATTACHMENTS.includes(authenticatorAttachment)
82
+ ) {
83
+ throw new TypeError(
84
+ `authenticatorAttachment must be one of ${AUTHENTICATOR_ATTACHMENTS.join(', ')}, not '${authenticatorAttachment}'`
85
+ );
86
+ }
87
+ if (
88
+ authenticatorType != null &&
89
+ authenticatorAttachment != null &&
90
+ authenticatorType !== authenticatorAttachment
91
+ ) {
92
+ throw new TypeError(
93
+ `authenticatorType '${authenticatorType}' and authenticatorAttachment '${authenticatorAttachment}' disagree; give one`
94
+ );
95
+ }
96
+ const choice = authenticatorType ?? authenticatorAttachment ?? fallback;
97
+ return choice === 'any' ? undefined : choice;
98
+ }
99
+
100
+ /**
101
+ * `authenticatorSelection` for a registration. Only the discoverable-credential
102
+ * policy comes from `options` and `configureWebAuthn`; an `authenticatorAttachment`
103
+ * or `userVerification` in `options` is not read here. A registration that
104
+ * wants an attachment adds it itself — see `resolveAuthenticatorAttachment`.
105
+ */
47
106
  export function buildAuthenticatorSelection(options = {}) {
48
107
  const {
49
108
  discoverableCredentials,
@@ -24,6 +24,7 @@ import {
24
24
  } from '../errors.js';
25
25
  import {
26
26
  buildAuthenticatorSelection,
27
+ resolveAuthenticatorAttachment,
27
28
  buildCredentialRequestOptions,
28
29
  } from './config.js';
29
30
  import { logWebAuthnResponse } from './debug-log.js';
@@ -115,6 +116,13 @@ export class WebAuthnDIDProvider {
115
116
  * @param {boolean} options.encryptKeystore - Enable keystore encryption
116
117
  * @param {string} options.keystoreEncryptionMethod - 'prf' (default), 'hmac-secret', or 'largeBlob'
117
118
  * @param {boolean} [options.discoverableCredentials] - Override global discoverable credential policy
119
+ * @param {'platform'|'cross-platform'|'any'} [options.authenticatorType='any'] -
120
+ * Which authenticators the browser offers: the device's own, a security
121
+ * key or phone, or every kind. `'any'` names none in the request, which is
122
+ * what this has sent since 0.2.8.
123
+ * @param {'platform'|'cross-platform'} [options.authenticatorAttachment] -
124
+ * The WebAuthn name for the same choice; refused if it disagrees with
125
+ * `authenticatorType`.
118
126
  * @returns {Promise<Object>} Credential info with public key and metadata.
119
127
  */
120
128
  static async createCredential(options = {}) {
@@ -131,10 +139,14 @@ export class WebAuthnDIDProvider {
131
139
  ...options,
132
140
  };
133
141
 
142
+ // Before any prompt: a choice this cannot honour is the caller's mistake.
143
+ const authenticatorAttachment = resolveAuthenticatorAttachment(options);
144
+
134
145
  webauthnLog('createCredential() called with options: %o', {
135
146
  userId,
136
147
  displayName,
137
148
  domain,
149
+ authenticatorAttachment: authenticatorAttachment ?? 'any',
138
150
  });
139
151
 
140
152
  if (!this.isSupported()) {
@@ -177,11 +189,14 @@ export class WebAuthnDIDProvider {
177
189
  { alg: -7, type: 'public-key' }, // ES256 (P-256 curve)
178
190
  { alg: -257, type: 'public-key' }, // RS256 fallback
179
191
  ],
180
- authenticatorSelection: buildAuthenticatorSelection({
181
- ...options,
182
- authenticatorAttachment: 'platform',
183
- userVerification: 'required',
184
- }),
192
+ // This named 'platform' from March to October 2026 without effect:
193
+ // buildAuthenticatorSelection never passed it on, so every kind of
194
+ // authenticator was offered, security keys included. That stays the
195
+ // default; `authenticatorType` narrows it.
196
+ authenticatorSelection: {
197
+ ...buildAuthenticatorSelection(options),
198
+ ...(authenticatorAttachment && { authenticatorAttachment }),
199
+ },
185
200
  timeout: 60000,
186
201
  attestation: 'none', // Don't need attestation for DID creation
187
202
  },
package/types/index.d.ts CHANGED
@@ -131,8 +131,14 @@ export interface CreateCredentialOptions {
131
131
  encryptKeystore?: boolean;
132
132
  keystoreEncryptionMethod?: KeystoreEncryptionMethod;
133
133
  discoverableCredentials?: boolean;
134
+ /**
135
+ * Which authenticators the browser offers: the device's own, a security key
136
+ * or phone, or every kind. Default `'any'`: the request names none.
137
+ */
134
138
  authenticatorType?: AuthenticatorType;
139
+ /** The WebAuthn name for `authenticatorType`; the two must not disagree. */
135
140
  authenticatorAttachment?: AuthenticatorAttachment;
141
+ /** Not read: a registration always requires user verification. */
136
142
  userVerification?: UserVerificationRequirement;
137
143
  [key: string]: unknown;
138
144
  }
@@ -140,6 +146,7 @@ export interface CreateCredentialOptions {
140
146
  export interface WebAuthnConfig {
141
147
  discoverableCredentials?: boolean;
142
148
  userVerification?: UserVerificationRequirement;
149
+ /** Not read by `configureWebAuthn`; give `createCredential` an `authenticatorType`. */
143
150
  authenticatorAttachment?: AuthenticatorAttachment;
144
151
  residentKey?: ResidentKeyRequirement;
145
152
  requireResidentKey?: boolean;