@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 +19 -0
- package/README.md +1 -0
- package/docs/API.md +11 -1
- package/package.json +1 -1
- package/src/webauthn/config.js +59 -0
- package/src/webauthn/provider.js +20 -5
- package/types/index.d.ts +7 -0
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.
|
|
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",
|
package/src/webauthn/config.js
CHANGED
|
@@ -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,
|
package/src/webauthn/provider.js
CHANGED
|
@@ -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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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;
|