humankey 0.2.0 → 0.3.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/README.md CHANGED
@@ -25,7 +25,12 @@ humankey/
25
25
  │ ├── verify.ts # Server-side proof verification (humankey/verify)
26
26
  │ ├── registration-verify.ts # Server-side registration verification
27
27
  │ ├── challenge.ts # Server-side challenge generation
28
+ │ ├── adapter-core.ts # Shared handler logic for all framework adapters
28
29
  │ ├── express.ts # Express framework adapter (humankey/express)
30
+ │ ├── nextjs.ts # Next.js App Router adapter (humankey/nextjs)
31
+ │ ├── hono.ts # Hono adapter (humankey/hono)
32
+ │ ├── fastify.ts # Fastify plugin (humankey/fastify)
33
+ │ ├── react.ts # React hook (humankey/react)
29
34
  │ ├── hash.ts # SHA-256 canonical JSON hashing (isomorphic)
30
35
  │ ├── types.ts # All type definitions
31
36
  │ └── errors.ts # Typed error classes
@@ -36,10 +41,14 @@ humankey/
36
41
  └── examples/basic/ # Working Express + HTML example
37
42
  ```
38
43
 
39
- **Three entry points:**
44
+ **Seven entry points:**
40
45
  - `humankey` — browser SDK (confirm + tap + register)
41
46
  - `humankey/verify` — server-side verification, registration, and challenge generation (any JS runtime)
42
47
  - `humankey/express` — Express router with built-in challenge lifecycle, registration, and verification
48
+ - `humankey/nextjs` — Next.js App Router route handlers
49
+ - `humankey/hono` — Hono app with humankey routes
50
+ - `humankey/fastify` — Fastify plugin
51
+ - `humankey/react` — React hook for the confirm → tap flow
43
52
 
44
53
  ## How It Works
45
54
 
@@ -61,10 +70,18 @@ npm install humankey @simplewebauthn/browser
61
70
 
62
71
  `@simplewebauthn/browser` is a peer dependency (only needed in the browser).
63
72
 
64
- For the Express adapter:
73
+ For framework adapters, install the framework alongside humankey:
65
74
 
66
75
  ```bash
67
- npm install humankey express
76
+ npm install humankey express # Express
77
+ npm install humankey hono # Hono
78
+ npm install humankey fastify # Fastify
79
+ ```
80
+
81
+ For the React hook:
82
+
83
+ ```bash
84
+ npm install humankey @simplewebauthn/browser react
68
85
  ```
69
86
 
70
87
  ## Usage
@@ -143,6 +160,146 @@ class RedisChallengeStore implements ChallengeStore {
143
160
  }
144
161
  ```
145
162
 
163
+ ### Next.js Adapter
164
+
165
+ For Next.js App Router. Each route handler is a separate file:
166
+
167
+ ```ts
168
+ // app/api/humankey/challenge/route.ts
169
+ import { createHumanKeyHandlers } from 'humankey/nextjs';
170
+ import type { TapCredential } from 'humankey/verify';
171
+
172
+ const credentials = new Map<string, TapCredential>();
173
+
174
+ const hk = createHumanKeyHandlers({
175
+ rpID: 'example.com',
176
+ rpName: 'My App',
177
+ origin: 'https://example.com',
178
+ getCredential: async (id) => credentials.get(id) ?? null,
179
+ onRegister: async (credential) => {
180
+ credentials.set(credential.id, credential);
181
+ },
182
+ });
183
+
184
+ export const POST = hk.challenge;
185
+ ```
186
+
187
+ ```ts
188
+ // app/api/humankey/register/route.ts
189
+ export const POST = hk.register;
190
+
191
+ // app/api/humankey/verify/route.ts
192
+ export const POST = hk.verify;
193
+ ```
194
+
195
+ Uses the Web `Request`/`Response` API — no Next.js-specific types required.
196
+
197
+ ### Hono Adapter
198
+
199
+ ```ts
200
+ import { Hono } from 'hono';
201
+ import { createHumanKeyApp } from 'humankey/hono';
202
+ import type { TapCredential } from 'humankey/verify';
203
+
204
+ const app = new Hono();
205
+ const credentials = new Map<string, TapCredential>();
206
+
207
+ app.route('/api', createHumanKeyApp({
208
+ rpID: 'example.com',
209
+ rpName: 'My App',
210
+ origin: 'https://example.com',
211
+ getCredential: async (id) => credentials.get(id) ?? null,
212
+ onRegister: async (credential) => {
213
+ credentials.set(credential.id, credential);
214
+ },
215
+ }));
216
+
217
+ export default app;
218
+ ```
219
+
220
+ ### Fastify Adapter
221
+
222
+ ```ts
223
+ import Fastify from 'fastify';
224
+ import { humanKeyPlugin } from 'humankey/fastify';
225
+ import type { TapCredential } from 'humankey/verify';
226
+
227
+ const app = Fastify();
228
+ const credentials = new Map<string, TapCredential>();
229
+
230
+ app.register(humanKeyPlugin, {
231
+ prefix: '/api',
232
+ rpID: 'example.com',
233
+ rpName: 'My App',
234
+ origin: 'https://example.com',
235
+ getCredential: async (id) => credentials.get(id) ?? null,
236
+ onRegister: async (credential) => {
237
+ credentials.set(credential.id, credential);
238
+ },
239
+ });
240
+
241
+ app.listen({ port: 3000 });
242
+ ```
243
+
244
+ ### React Hook
245
+
246
+ The `useHumanKey` hook manages the full client-side flow: fetch challenge, show confirmation code, trigger hardware key tap, and verify.
247
+
248
+ ```tsx
249
+ import { useHumanKey } from 'humankey/react';
250
+
251
+ function TransferButton({ credentialId }: { credentialId: string }) {
252
+ const {
253
+ status,
254
+ confirmationCode,
255
+ error,
256
+ startAction,
257
+ confirmCode,
258
+ reset,
259
+ } = useHumanKey({ rpID: 'example.com', apiBase: '/api' });
260
+
261
+ const handleTransfer = async () => {
262
+ // Step 1: Start the action — fetches challenge, generates confirmation code
263
+ await startAction(
264
+ { action: 'transfer', data: { to: 'bob', amount: 100 } },
265
+ [{ id: credentialId }],
266
+ );
267
+ // status is now 'confirming', confirmationCode is e.g. 'A7X3'
268
+ };
269
+
270
+ const handleConfirm = async (userInput: string) => {
271
+ // Step 2: User typed the code — triggers YubiKey tap and server verification
272
+ const proof = await confirmCode(userInput);
273
+ // status is now 'verified', proof contains the signed assertion
274
+ };
275
+
276
+ return (
277
+ <div>
278
+ {status === 'idle' && <button onClick={handleTransfer}>Send $100</button>}
279
+ {status === 'confirming' && (
280
+ <div>
281
+ <p>Type this code: <strong>{confirmationCode}</strong></p>
282
+ <input onKeyDown={(e) => {
283
+ if (e.key === 'Enter') handleConfirm(e.currentTarget.value);
284
+ }} />
285
+ </div>
286
+ )}
287
+ {status === 'tapping' && <p>Tap your YubiKey...</p>}
288
+ {status === 'verified' && <p>Transfer approved!</p>}
289
+ {status === 'error' && <p>Error: {error?.message} <button onClick={reset}>Retry</button></p>}
290
+ </div>
291
+ );
292
+ }
293
+ ```
294
+
295
+ The hook also exposes a `register` function for one-time key registration:
296
+
297
+ ```tsx
298
+ const { register } = useHumanKey({ rpID: 'example.com' });
299
+ const result = await register('alice');
300
+ // result.credentialId — store this for future use
301
+ ```
302
+
146
303
  ### Server (manual — challenge + registration + verify)
147
304
 
148
305
  ```ts
@@ -310,7 +467,11 @@ For production, consider:
310
467
  |---|---|---|
311
468
  | `humankey` | Browser | `createConfirmation`, `requestTap`, `registerKey`, `isHumanKeySupported`, `hashAction`, `HumanKeyError` |
312
469
  | `humankey/verify` | Server (any JS runtime) | `verifyTapProof`, `verifyRegistration`, `createChallenge`, `HumanKeyError` |
313
- | `humankey/express` | Server (Express) | `createHumanKeyRouter`, `MemoryChallengeStore`, `ChallengeStore`, `HumanKeyExpressConfig` |
470
+ | `humankey/express` | Server (Express) | `createHumanKeyRouter`, `MemoryChallengeStore`, `ChallengeStore` |
471
+ | `humankey/nextjs` | Server (Next.js App Router) | `createHumanKeyHandlers`, `MemoryChallengeStore`, `ChallengeStore` |
472
+ | `humankey/hono` | Server (Hono) | `createHumanKeyApp`, `MemoryChallengeStore`, `ChallengeStore` |
473
+ | `humankey/fastify` | Server (Fastify) | `humanKeyPlugin`, `MemoryChallengeStore`, `ChallengeStore` |
474
+ | `humankey/react` | Browser (React) | `useHumanKey` |
314
475
 
315
476
  ### Error Codes
316
477
 
@@ -0,0 +1,41 @@
1
+ import { c as TapCredential, V as VerifyResult, A as ActionPayload } from './types-By44RNIt.js';
2
+
3
+ /** Abstraction for challenge storage with TTL and single-use semantics. */
4
+ interface ChallengeStore {
5
+ /** Store a challenge with a TTL. */
6
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
7
+ /** Retrieve and delete a challenge (single-use). Returns null if expired or not found. */
8
+ get(id: string): Promise<string | null>;
9
+ }
10
+ /** In-memory challenge store with TTL enforcement. Use a database or Redis in production. */
11
+ declare class MemoryChallengeStore implements ChallengeStore {
12
+ private store;
13
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
14
+ get(id: string): Promise<string | null>;
15
+ private cleanup;
16
+ }
17
+ /** Shared configuration for all framework adapters. */
18
+ interface HumanKeyAdapterConfig {
19
+ /** Relying party ID, e.g. "example.com" */
20
+ rpID: string;
21
+ /** Relying party display name */
22
+ rpName: string;
23
+ /** Expected origin(s), e.g. "https://example.com" */
24
+ origin: string | string[];
25
+ /** Challenge TTL in ms (default: 60000) */
26
+ challengeTTL?: number;
27
+ /** Custom challenge store (default: MemoryChallengeStore) */
28
+ challengeStore?: ChallengeStore;
29
+ /** Look up a stored credential by ID */
30
+ getCredential: (credentialId: string) => Promise<TapCredential | null>;
31
+ /** Called after successful registration — store the credential */
32
+ onRegister: (credential: TapCredential) => Promise<void>;
33
+ /** Called after successful tap verification */
34
+ onVerify?: (result: VerifyResult, action: ActionPayload) => Promise<void>;
35
+ /** Require user verification (default: true) */
36
+ requireUserVerification?: boolean;
37
+ /** Restrict to specific authenticator models by AAGUID */
38
+ allowedAAGUIDs?: string[];
39
+ }
40
+
41
+ export { type ChallengeStore as C, type HumanKeyAdapterConfig as H, MemoryChallengeStore as M };
@@ -0,0 +1,41 @@
1
+ import { c as TapCredential, V as VerifyResult, A as ActionPayload } from './types-By44RNIt.cjs';
2
+
3
+ /** Abstraction for challenge storage with TTL and single-use semantics. */
4
+ interface ChallengeStore {
5
+ /** Store a challenge with a TTL. */
6
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
7
+ /** Retrieve and delete a challenge (single-use). Returns null if expired or not found. */
8
+ get(id: string): Promise<string | null>;
9
+ }
10
+ /** In-memory challenge store with TTL enforcement. Use a database or Redis in production. */
11
+ declare class MemoryChallengeStore implements ChallengeStore {
12
+ private store;
13
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
14
+ get(id: string): Promise<string | null>;
15
+ private cleanup;
16
+ }
17
+ /** Shared configuration for all framework adapters. */
18
+ interface HumanKeyAdapterConfig {
19
+ /** Relying party ID, e.g. "example.com" */
20
+ rpID: string;
21
+ /** Relying party display name */
22
+ rpName: string;
23
+ /** Expected origin(s), e.g. "https://example.com" */
24
+ origin: string | string[];
25
+ /** Challenge TTL in ms (default: 60000) */
26
+ challengeTTL?: number;
27
+ /** Custom challenge store (default: MemoryChallengeStore) */
28
+ challengeStore?: ChallengeStore;
29
+ /** Look up a stored credential by ID */
30
+ getCredential: (credentialId: string) => Promise<TapCredential | null>;
31
+ /** Called after successful registration — store the credential */
32
+ onRegister: (credential: TapCredential) => Promise<void>;
33
+ /** Called after successful tap verification */
34
+ onVerify?: (result: VerifyResult, action: ActionPayload) => Promise<void>;
35
+ /** Require user verification (default: true) */
36
+ requireUserVerification?: boolean;
37
+ /** Restrict to specific authenticator models by AAGUID */
38
+ allowedAAGUIDs?: string[];
39
+ }
40
+
41
+ export { type ChallengeStore as C, type HumanKeyAdapterConfig as H, MemoryChallengeStore as M };
@@ -0,0 +1,27 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+ var _chunkJI6NIMGKcjs = require('./chunk-JI6NIMGK.cjs');
7
+
8
+ // src/confirm.ts
9
+ async function createConfirmation(action) {
10
+ const hashBuffer = await _chunkJI6NIMGKcjs.hashAction.call(void 0, action);
11
+ const code = _chunkJI6NIMGKcjs.deriveConfirmationCode.call(void 0, hashBuffer);
12
+ const actionHash = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, hashBuffer);
13
+ return { code, actionHash };
14
+ }
15
+ function validateConfirmation(confirmation, userInput) {
16
+ if (userInput.toUpperCase().trim() !== confirmation.code.toUpperCase()) {
17
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
18
+ "Confirmation code mismatch",
19
+ "CONFIRMATION_MISMATCH"
20
+ );
21
+ }
22
+ }
23
+
24
+
25
+
26
+
27
+ exports.createConfirmation = createConfirmation; exports.validateConfirmation = validateConfirmation;
@@ -0,0 +1,124 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
2
+
3
+
4
+
5
+ var _chunkFW3YUVJCcjs = require('./chunk-FW3YUVJC.cjs');
6
+
7
+
8
+ var _chunkJI6NIMGKcjs = require('./chunk-JI6NIMGK.cjs');
9
+
10
+ // src/adapter-core.ts
11
+ var MemoryChallengeStore = (_class = class {constructor() { _class.prototype.__init.call(this); }
12
+ __init() {this.store = /* @__PURE__ */ new Map()}
13
+ async set(id, challenge, ttlMs) {
14
+ this.store.set(id, { challenge, expiresAt: Date.now() + ttlMs });
15
+ this.cleanup();
16
+ }
17
+ async get(id) {
18
+ const entry = this.store.get(id);
19
+ if (!entry) return null;
20
+ this.store.delete(id);
21
+ if (Date.now() > entry.expiresAt) return null;
22
+ return entry.challenge;
23
+ }
24
+ cleanup() {
25
+ const now = Date.now();
26
+ for (const [id, entry] of this.store) {
27
+ if (now > entry.expiresAt) this.store.delete(id);
28
+ }
29
+ }
30
+ }, _class);
31
+ function resolveConfig(config) {
32
+ return {
33
+ rpID: config.rpID,
34
+ rpName: config.rpName,
35
+ origin: config.origin,
36
+ challengeTTL: _nullishCoalesce(config.challengeTTL, () => ( 6e4)),
37
+ challengeStore: _nullishCoalesce(config.challengeStore, () => ( new MemoryChallengeStore())),
38
+ getCredential: config.getCredential,
39
+ onRegister: config.onRegister,
40
+ onVerify: config.onVerify,
41
+ requireUserVerification: _nullishCoalesce(config.requireUserVerification, () => ( true)),
42
+ allowedAAGUIDs: config.allowedAAGUIDs
43
+ };
44
+ }
45
+ async function handleChallenge(config) {
46
+ try {
47
+ const challenge = _chunkFW3YUVJCcjs.createChallenge.call(void 0, );
48
+ const challengeId = crypto.randomUUID();
49
+ await config.challengeStore.set(challengeId, challenge, config.challengeTTL);
50
+ return { status: 200, body: { challengeId, challenge } };
51
+ } catch (e) {
52
+ return { status: 500, body: { error: "Internal server error" } };
53
+ }
54
+ }
55
+ async function handleRegister(config, body) {
56
+ try {
57
+ const challenge = await config.challengeStore.get(body.challengeId);
58
+ if (!challenge) {
59
+ return { status: 400, body: { error: "Challenge not found or expired" } };
60
+ }
61
+ const result = await _chunkFW3YUVJCcjs.verifyRegistration.call(void 0, {
62
+ response: body.response,
63
+ expectedChallenge: challenge,
64
+ expectedOrigin: config.origin,
65
+ expectedRPID: config.rpID,
66
+ requireUserVerification: config.requireUserVerification,
67
+ allowedAAGUIDs: config.allowedAAGUIDs
68
+ });
69
+ await config.onRegister(result.credential);
70
+ return { status: 200, body: { ok: true, credentialId: result.credential.id } };
71
+ } catch (error) {
72
+ return errorResult(error);
73
+ }
74
+ }
75
+ async function handleVerify(config, body) {
76
+ try {
77
+ const challenge = await config.challengeStore.get(body.challengeId);
78
+ if (!challenge) {
79
+ return { status: 400, body: { error: "Challenge not found or expired" } };
80
+ }
81
+ const proof = body.proof;
82
+ const credential = await config.getCredential(_optionalChain([proof, 'access', _ => _.response, 'optionalAccess', _2 => _2.id]));
83
+ if (!credential) {
84
+ return { status: 400, body: { error: "Credential not found" } };
85
+ }
86
+ const result = await _chunkFW3YUVJCcjs.verifyTapProof.call(void 0, {
87
+ proof,
88
+ credential,
89
+ expectedChallenge: challenge,
90
+ expectedAction: body.action,
91
+ expectedOrigin: config.origin,
92
+ expectedRPID: config.rpID,
93
+ requireUserVerification: config.requireUserVerification
94
+ });
95
+ credential.counter = result.newCounter;
96
+ if (config.onVerify) {
97
+ await config.onVerify(result, body.action);
98
+ }
99
+ return {
100
+ status: 200,
101
+ body: {
102
+ verified: result.verified,
103
+ confirmationValid: result.confirmationValid,
104
+ userVerified: result.userVerified
105
+ }
106
+ };
107
+ } catch (error) {
108
+ return errorResult(error);
109
+ }
110
+ }
111
+ function errorResult(error) {
112
+ if (error instanceof _chunkJI6NIMGKcjs.HumanKeyError) {
113
+ return { status: 400, body: { error: error.message, code: error.code } };
114
+ }
115
+ return { status: 500, body: { error: "Internal server error" } };
116
+ }
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+ exports.MemoryChallengeStore = MemoryChallengeStore; exports.resolveConfig = resolveConfig; exports.handleChallenge = handleChallenge; exports.handleRegister = handleRegister; exports.handleVerify = handleVerify;
@@ -0,0 +1,27 @@
1
+ import {
2
+ HumanKeyError,
3
+ bufferToBase64url,
4
+ deriveConfirmationCode,
5
+ hashAction
6
+ } from "./chunk-CLQSCDXC.mjs";
7
+
8
+ // src/confirm.ts
9
+ async function createConfirmation(action) {
10
+ const hashBuffer = await hashAction(action);
11
+ const code = deriveConfirmationCode(hashBuffer);
12
+ const actionHash = bufferToBase64url(hashBuffer);
13
+ return { code, actionHash };
14
+ }
15
+ function validateConfirmation(confirmation, userInput) {
16
+ if (userInput.toUpperCase().trim() !== confirmation.code.toUpperCase()) {
17
+ throw new HumanKeyError(
18
+ "Confirmation code mismatch",
19
+ "CONFIRMATION_MISMATCH"
20
+ );
21
+ }
22
+ }
23
+
24
+ export {
25
+ createConfirmation,
26
+ validateConfirmation
27
+ };
@@ -0,0 +1,75 @@
1
+ import {
2
+ validateConfirmation
3
+ } from "./chunk-G2X46ZRM.mjs";
4
+ import {
5
+ HumanKeyError,
6
+ base64urlToBuffer,
7
+ bufferToBase64url,
8
+ combineAndHash,
9
+ hashAction
10
+ } from "./chunk-CLQSCDXC.mjs";
11
+
12
+ // src/tap.ts
13
+ import { startAuthentication } from "@simplewebauthn/browser";
14
+ async function requestTap(request) {
15
+ const {
16
+ challenge,
17
+ action,
18
+ confirmation,
19
+ userInput,
20
+ allowCredentials,
21
+ rpID,
22
+ userVerification = "required",
23
+ timeout = 6e4
24
+ } = request;
25
+ validateConfirmation(confirmation, userInput);
26
+ const actionHashBuffer = await hashAction(action);
27
+ const confirmationInput = `${confirmation.code}:${userInput.toUpperCase().trim()}`;
28
+ const confirmationHashBuffer = await crypto.subtle.digest(
29
+ "SHA-256",
30
+ new TextEncoder().encode(confirmationInput)
31
+ );
32
+ const challengeBuffer = base64urlToBuffer(challenge);
33
+ const finalChallenge = await combineAndHash(
34
+ challengeBuffer,
35
+ actionHashBuffer,
36
+ confirmationHashBuffer
37
+ );
38
+ const actionHash = bufferToBase64url(actionHashBuffer);
39
+ const confirmationHash = bufferToBase64url(confirmationHashBuffer);
40
+ try {
41
+ const response = await startAuthentication({
42
+ optionsJSON: {
43
+ challenge: bufferToBase64url(finalChallenge),
44
+ rpId: rpID,
45
+ allowCredentials: allowCredentials.map((cred) => ({
46
+ id: cred.id,
47
+ type: "public-key",
48
+ transports: cred.transports
49
+ })),
50
+ userVerification,
51
+ timeout
52
+ }
53
+ });
54
+ return {
55
+ response,
56
+ action,
57
+ actionHash,
58
+ confirmationHash,
59
+ userInput: userInput.toUpperCase().trim()
60
+ };
61
+ } catch (error) {
62
+ if (error instanceof Error && error.name === "NotAllowedError") {
63
+ throw new HumanKeyError(
64
+ "User cancelled the hardware key tap",
65
+ "USER_CANCELLED",
66
+ error
67
+ );
68
+ }
69
+ throw error;
70
+ }
71
+ }
72
+
73
+ export {
74
+ requestTap
75
+ };
@@ -0,0 +1,76 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+ var _chunkJI6NIMGKcjs = require('./chunk-JI6NIMGK.cjs');
5
+
6
+ // src/register.ts
7
+ var _browser = require('@simplewebauthn/browser');
8
+ async function registerKey(request) {
9
+ const {
10
+ challenge,
11
+ rpID,
12
+ rpName,
13
+ userName,
14
+ userDisplayName = userName,
15
+ excludeCredentials = [],
16
+ attestation = "direct",
17
+ userVerification = "required",
18
+ timeout = 6e4
19
+ } = request;
20
+ const userIdBytes = new TextEncoder().encode(userName);
21
+ const userIdHash = await crypto.subtle.digest("SHA-256", userIdBytes);
22
+ const userId = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, userIdHash);
23
+ try {
24
+ const response = await _browser.startRegistration.call(void 0, {
25
+ optionsJSON: {
26
+ rp: { name: rpName, id: rpID },
27
+ user: {
28
+ id: userId,
29
+ name: userName,
30
+ displayName: userDisplayName
31
+ },
32
+ challenge,
33
+ pubKeyCredParams: [
34
+ { alg: -7, type: "public-key" },
35
+ // ES256
36
+ { alg: -257, type: "public-key" }
37
+ // RS256
38
+ ],
39
+ timeout,
40
+ attestation,
41
+ excludeCredentials: excludeCredentials.map((cred) => ({
42
+ id: cred.id,
43
+ type: "public-key",
44
+ transports: cred.transports
45
+ })),
46
+ authenticatorSelection: {
47
+ authenticatorAttachment: "cross-platform",
48
+ residentKey: "preferred",
49
+ userVerification
50
+ }
51
+ }
52
+ });
53
+ return {
54
+ credentialId: response.id,
55
+ response,
56
+ transports: response.response.transports
57
+ };
58
+ } catch (error) {
59
+ if (error instanceof Error && error.name === "NotAllowedError") {
60
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
61
+ "User cancelled hardware key registration",
62
+ "USER_CANCELLED",
63
+ error
64
+ );
65
+ }
66
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
67
+ `Registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
68
+ "REGISTRATION_FAILED",
69
+ error
70
+ );
71
+ }
72
+ }
73
+
74
+
75
+
76
+ exports.registerKey = registerKey;