humankey 0.2.0

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.
@@ -0,0 +1,81 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/hash.ts
2
+ async function hashAction(action) {
3
+ const canonical = canonicalize(action);
4
+ const encoded = new TextEncoder().encode(canonical);
5
+ return crypto.subtle.digest("SHA-256", encoded);
6
+ }
7
+ async function combineAndHash(...buffers) {
8
+ let totalLength = 0;
9
+ for (const buf of buffers) {
10
+ totalLength += buf.byteLength;
11
+ }
12
+ const combined = new Uint8Array(totalLength);
13
+ let offset = 0;
14
+ for (const buf of buffers) {
15
+ combined.set(new Uint8Array(buf), offset);
16
+ offset += buf.byteLength;
17
+ }
18
+ return crypto.subtle.digest("SHA-256", combined);
19
+ }
20
+ function deriveConfirmationCode(hashBuffer) {
21
+ const bytes = new Uint8Array(hashBuffer);
22
+ const CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
23
+ let code = "";
24
+ for (let i = 0; i < 4; i++) {
25
+ const value = bytes[i * 2] << 8 | bytes[i * 2 + 1];
26
+ code += CHARSET[value % CHARSET.length];
27
+ }
28
+ return code;
29
+ }
30
+ function bufferToBase64url(buffer) {
31
+ const bytes = new Uint8Array(buffer);
32
+ let binary = "";
33
+ for (let i = 0; i < bytes.length; i++) {
34
+ binary += String.fromCharCode(bytes[i]);
35
+ }
36
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
37
+ }
38
+ function base64urlToBuffer(base64url) {
39
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
40
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
41
+ const binary = atob(padded);
42
+ const bytes = new Uint8Array(binary.length);
43
+ for (let i = 0; i < binary.length; i++) {
44
+ bytes[i] = binary.charCodeAt(i);
45
+ }
46
+ return bytes.buffer;
47
+ }
48
+ function canonicalize(value) {
49
+ if (value === null || typeof value !== "object") {
50
+ return JSON.stringify(value);
51
+ }
52
+ if (Array.isArray(value)) {
53
+ return "[" + value.map(canonicalize).join(",") + "]";
54
+ }
55
+ const obj = value;
56
+ const keys = Object.keys(obj).sort();
57
+ const pairs = keys.map((key) => JSON.stringify(key) + ":" + canonicalize(obj[key]));
58
+ return "{" + pairs.join(",") + "}";
59
+ }
60
+
61
+ // src/errors.ts
62
+ var HumanKeyError = class extends Error {
63
+
64
+ constructor(message, code, cause) {
65
+ super(message);
66
+ this.name = "HumanKeyError";
67
+ this.code = code;
68
+ if (cause !== void 0) {
69
+ this.cause = cause;
70
+ }
71
+ }
72
+ };
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+ exports.hashAction = hashAction; exports.combineAndHash = combineAndHash; exports.deriveConfirmationCode = deriveConfirmationCode; exports.bufferToBase64url = bufferToBase64url; exports.base64urlToBuffer = base64urlToBuffer; exports.HumanKeyError = HumanKeyError;
@@ -0,0 +1,174 @@
1
+ import {
2
+ HumanKeyError,
3
+ base64urlToBuffer,
4
+ bufferToBase64url,
5
+ combineAndHash,
6
+ deriveConfirmationCode,
7
+ hashAction
8
+ } from "./chunk-CLQSCDXC.mjs";
9
+
10
+ // src/verify.ts
11
+ import { verifyAuthenticationResponse } from "@simplewebauthn/server";
12
+
13
+ // src/challenge.ts
14
+ function createChallenge(byteLength = 32) {
15
+ const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
16
+ return bufferToBase64url(bytes.buffer);
17
+ }
18
+
19
+ // src/registration-verify.ts
20
+ import { verifyRegistrationResponse } from "@simplewebauthn/server";
21
+ async function verifyRegistration(request) {
22
+ const {
23
+ response,
24
+ expectedChallenge,
25
+ expectedOrigin,
26
+ expectedRPID,
27
+ requireUserVerification = true,
28
+ allowedAAGUIDs
29
+ } = request;
30
+ let verification;
31
+ try {
32
+ verification = await verifyRegistrationResponse({
33
+ response,
34
+ expectedChallenge,
35
+ expectedOrigin,
36
+ expectedRPID,
37
+ requireUserVerification
38
+ });
39
+ } catch (error) {
40
+ throw new HumanKeyError(
41
+ `Registration verification failed: ${error instanceof Error ? error.message : "Unknown error"}`,
42
+ "REGISTRATION_FAILED",
43
+ error
44
+ );
45
+ }
46
+ if (!verification.verified || !verification.registrationInfo) {
47
+ throw new HumanKeyError(
48
+ "Registration verification failed",
49
+ "REGISTRATION_FAILED"
50
+ );
51
+ }
52
+ const { registrationInfo } = verification;
53
+ if (allowedAAGUIDs && allowedAAGUIDs.length > 0) {
54
+ if (!allowedAAGUIDs.includes(registrationInfo.aaguid)) {
55
+ throw new HumanKeyError(
56
+ `Authenticator model (AAGUID ${registrationInfo.aaguid}) is not in the allowed list`,
57
+ "AAGUID_NOT_ALLOWED"
58
+ );
59
+ }
60
+ }
61
+ const credential = {
62
+ id: registrationInfo.credential.id,
63
+ publicKey: registrationInfo.credential.publicKey,
64
+ counter: registrationInfo.credential.counter,
65
+ transports: registrationInfo.credential.transports,
66
+ deviceType: registrationInfo.credentialDeviceType,
67
+ backedUp: registrationInfo.credentialBackedUp,
68
+ aaguid: registrationInfo.aaguid
69
+ };
70
+ return { credential, verified: true };
71
+ }
72
+
73
+ // src/verify.ts
74
+ async function verifyTapProof(request) {
75
+ const {
76
+ proof,
77
+ credential,
78
+ expectedChallenge,
79
+ expectedAction,
80
+ expectedOrigin,
81
+ expectedRPID,
82
+ requireUserVerification = true
83
+ } = request;
84
+ const actionHashBuffer = await hashAction(expectedAction);
85
+ const expectedActionHash = bufferToBase64url(actionHashBuffer);
86
+ if (proof.actionHash !== expectedActionHash) {
87
+ throw new HumanKeyError(
88
+ "Action hash mismatch \u2014 the client may have signed a different action than expected",
89
+ "ACTION_HASH_MISMATCH"
90
+ );
91
+ }
92
+ const expectedCode = deriveConfirmationCode(actionHashBuffer);
93
+ const confirmationValid = proof.userInput.toUpperCase().trim() === expectedCode.toUpperCase();
94
+ const confirmationInput = `${expectedCode}:${proof.userInput.toUpperCase().trim()}`;
95
+ const confirmationHashBuffer = await crypto.subtle.digest(
96
+ "SHA-256",
97
+ new TextEncoder().encode(confirmationInput)
98
+ );
99
+ const challengeBuffer = base64urlToBuffer(expectedChallenge);
100
+ const expectedFinalChallenge = await combineAndHash(
101
+ challengeBuffer,
102
+ actionHashBuffer,
103
+ confirmationHashBuffer
104
+ );
105
+ const expectedFinalChallengeB64 = bufferToBase64url(expectedFinalChallenge);
106
+ let verification;
107
+ try {
108
+ verification = await verifyAuthenticationResponse({
109
+ response: proof.response,
110
+ expectedChallenge: expectedFinalChallengeB64,
111
+ expectedOrigin,
112
+ expectedRPID,
113
+ credential: {
114
+ id: credential.id,
115
+ publicKey: credential.publicKey,
116
+ counter: credential.counter,
117
+ transports: credential.transports
118
+ },
119
+ requireUserVerification
120
+ });
121
+ } catch (error) {
122
+ const message = error instanceof Error ? error.message : "Unknown error";
123
+ if (message.includes("counter") && message.includes("lower than expected")) {
124
+ throw new HumanKeyError(
125
+ `Signature counter replay detected \u2014 ${message}`,
126
+ "COUNTER_REPLAY",
127
+ error
128
+ );
129
+ }
130
+ throw new HumanKeyError(
131
+ `WebAuthn verification failed: ${message}`,
132
+ "VERIFICATION_FAILED",
133
+ error
134
+ );
135
+ }
136
+ if (!verification.verified) {
137
+ throw new HumanKeyError(
138
+ "WebAuthn signature verification failed",
139
+ "VERIFICATION_FAILED"
140
+ );
141
+ }
142
+ const { authenticationInfo } = verification;
143
+ if (requireUserVerification && !authenticationInfo.userVerified) {
144
+ throw new HumanKeyError(
145
+ "User verification was required but not performed \u2014 the authenticator did not verify the user (possible Safari clamshell mode issue)",
146
+ "USER_VERIFICATION_MISSING"
147
+ );
148
+ }
149
+ const requireConfirmation = request.requireConfirmation ?? true;
150
+ if (requireConfirmation && !confirmationValid) {
151
+ throw new HumanKeyError(
152
+ "Confirmation code mismatch",
153
+ "CONFIRMATION_MISMATCH"
154
+ );
155
+ }
156
+ if (authenticationInfo.newCounter <= credential.counter && credential.counter !== 0) {
157
+ throw new HumanKeyError(
158
+ `Signature counter did not increase (got ${authenticationInfo.newCounter}, expected > ${credential.counter}) \u2014 possible cloned key`,
159
+ "COUNTER_REPLAY"
160
+ );
161
+ }
162
+ return {
163
+ verified: true,
164
+ userVerified: authenticationInfo.userVerified,
165
+ confirmationValid,
166
+ newCounter: authenticationInfo.newCounter
167
+ };
168
+ }
169
+
170
+ export {
171
+ createChallenge,
172
+ verifyRegistration,
173
+ verifyTapProof
174
+ };
@@ -0,0 +1,7 @@
1
+ type HumanKeyErrorCode = 'CONFIRMATION_MISMATCH' | 'VERIFICATION_FAILED' | 'USER_VERIFICATION_MISSING' | 'COUNTER_REPLAY' | 'CHALLENGE_MISMATCH' | 'ACTION_HASH_MISMATCH' | 'WEBAUTHN_NOT_SUPPORTED' | 'USER_CANCELLED' | 'REGISTRATION_FAILED' | 'AAGUID_NOT_ALLOWED';
2
+ declare class HumanKeyError extends Error {
3
+ readonly code: HumanKeyErrorCode;
4
+ constructor(message: string, code: HumanKeyErrorCode, cause?: unknown);
5
+ }
6
+
7
+ export { HumanKeyError as H, type HumanKeyErrorCode as a };
@@ -0,0 +1,7 @@
1
+ type HumanKeyErrorCode = 'CONFIRMATION_MISMATCH' | 'VERIFICATION_FAILED' | 'USER_VERIFICATION_MISSING' | 'COUNTER_REPLAY' | 'CHALLENGE_MISMATCH' | 'ACTION_HASH_MISMATCH' | 'WEBAUTHN_NOT_SUPPORTED' | 'USER_CANCELLED' | 'REGISTRATION_FAILED' | 'AAGUID_NOT_ALLOWED';
2
+ declare class HumanKeyError extends Error {
3
+ readonly code: HumanKeyErrorCode;
4
+ constructor(message: string, code: HumanKeyErrorCode, cause?: unknown);
5
+ }
6
+
7
+ export { HumanKeyError as H, type HumanKeyErrorCode as a };
@@ -0,0 +1,124 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); 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/express.ts
11
+ var _express = require('express');
12
+ var MemoryChallengeStore = (_class = class {constructor() { _class.prototype.__init.call(this); }
13
+ __init() {this.store = /* @__PURE__ */ new Map()}
14
+ async set(id, challenge, ttlMs) {
15
+ this.store.set(id, { challenge, expiresAt: Date.now() + ttlMs });
16
+ this.cleanup();
17
+ }
18
+ async get(id) {
19
+ const entry = this.store.get(id);
20
+ if (!entry) return null;
21
+ this.store.delete(id);
22
+ if (Date.now() > entry.expiresAt) return null;
23
+ return entry.challenge;
24
+ }
25
+ cleanup() {
26
+ const now = Date.now();
27
+ for (const [id, entry] of this.store) {
28
+ if (now > entry.expiresAt) this.store.delete(id);
29
+ }
30
+ }
31
+ }, _class);
32
+ function createHumanKeyRouter(config) {
33
+ const {
34
+ rpID,
35
+ origin,
36
+ challengeTTL = 6e4,
37
+ challengeStore = new MemoryChallengeStore(),
38
+ getCredential,
39
+ onRegister,
40
+ onVerify,
41
+ requireUserVerification = true,
42
+ allowedAAGUIDs
43
+ } = config;
44
+ const router = _express.Router.call(void 0, );
45
+ router.post("/challenge", async (_req, res) => {
46
+ try {
47
+ const challenge = _chunkFW3YUVJCcjs.createChallenge.call(void 0, );
48
+ const challengeId = crypto.randomUUID();
49
+ await challengeStore.set(challengeId, challenge, challengeTTL);
50
+ res.json({ challengeId, challenge });
51
+ } catch (error) {
52
+ handleError(res, error);
53
+ }
54
+ });
55
+ router.post("/register", async (req, res) => {
56
+ try {
57
+ const { response, challengeId } = req.body;
58
+ const challenge = await challengeStore.get(challengeId);
59
+ if (!challenge) {
60
+ res.status(400).json({ error: "Challenge not found or expired" });
61
+ return;
62
+ }
63
+ const result = await _chunkFW3YUVJCcjs.verifyRegistration.call(void 0, {
64
+ response,
65
+ expectedChallenge: challenge,
66
+ expectedOrigin: origin,
67
+ expectedRPID: rpID,
68
+ requireUserVerification,
69
+ allowedAAGUIDs
70
+ });
71
+ await onRegister(result.credential);
72
+ res.json({ ok: true, credentialId: result.credential.id });
73
+ } catch (error) {
74
+ handleError(res, error);
75
+ }
76
+ });
77
+ router.post("/verify", async (req, res) => {
78
+ try {
79
+ const { proof, challengeId, action } = req.body;
80
+ const challenge = await challengeStore.get(challengeId);
81
+ if (!challenge) {
82
+ res.status(400).json({ error: "Challenge not found or expired" });
83
+ return;
84
+ }
85
+ const credential = await getCredential(_optionalChain([proof, 'access', _ => _.response, 'optionalAccess', _2 => _2.id]));
86
+ if (!credential) {
87
+ res.status(400).json({ error: "Credential not found" });
88
+ return;
89
+ }
90
+ const result = await _chunkFW3YUVJCcjs.verifyTapProof.call(void 0, {
91
+ proof,
92
+ credential,
93
+ expectedChallenge: challenge,
94
+ expectedAction: action,
95
+ expectedOrigin: origin,
96
+ expectedRPID: rpID,
97
+ requireUserVerification
98
+ });
99
+ credential.counter = result.newCounter;
100
+ if (onVerify) {
101
+ await onVerify(result, action);
102
+ }
103
+ res.json({
104
+ verified: result.verified,
105
+ confirmationValid: result.confirmationValid,
106
+ userVerified: result.userVerified
107
+ });
108
+ } catch (error) {
109
+ handleError(res, error);
110
+ }
111
+ });
112
+ return router;
113
+ }
114
+ function handleError(res, error) {
115
+ if (error instanceof _chunkJI6NIMGKcjs.HumanKeyError) {
116
+ res.status(400).json({ error: error.message, code: error.code });
117
+ } else {
118
+ res.status(500).json({ error: "Internal server error" });
119
+ }
120
+ }
121
+
122
+
123
+
124
+ exports.MemoryChallengeStore = MemoryChallengeStore; exports.createHumanKeyRouter = createHumanKeyRouter;
@@ -0,0 +1,52 @@
1
+ import { Router } from 'express';
2
+ import { c as TapCredential, V as VerifyResult, A as ActionPayload } from './types-By44RNIt.cjs';
3
+ import '@simplewebauthn/server';
4
+
5
+ /** Abstraction for challenge storage with TTL and single-use semantics. */
6
+ interface ChallengeStore {
7
+ /** Store a challenge with a TTL. */
8
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
9
+ /** Retrieve and delete a challenge (single-use). Returns null if expired or not found. */
10
+ get(id: string): Promise<string | null>;
11
+ }
12
+ /** In-memory challenge store with TTL enforcement. Use a database or Redis in production. */
13
+ declare class MemoryChallengeStore implements ChallengeStore {
14
+ private store;
15
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
16
+ get(id: string): Promise<string | null>;
17
+ private cleanup;
18
+ }
19
+ /** Configuration for the humankey Express router. */
20
+ interface HumanKeyExpressConfig {
21
+ /** Relying party ID, e.g. "example.com" */
22
+ rpID: string;
23
+ /** Relying party display name */
24
+ rpName: string;
25
+ /** Expected origin(s), e.g. "https://example.com" */
26
+ origin: string | string[];
27
+ /** Challenge TTL in ms (default: 60000) */
28
+ challengeTTL?: number;
29
+ /** Custom challenge store (default: MemoryChallengeStore) */
30
+ challengeStore?: ChallengeStore;
31
+ /** Look up a stored credential by ID */
32
+ getCredential: (credentialId: string) => Promise<TapCredential | null>;
33
+ /** Called after successful registration — store the credential */
34
+ onRegister: (credential: TapCredential) => Promise<void>;
35
+ /** Called after successful tap verification */
36
+ onVerify?: (result: VerifyResult, action: ActionPayload) => Promise<void>;
37
+ /** Require user verification (default: true) */
38
+ requireUserVerification?: boolean;
39
+ /** Restrict to specific authenticator models by AAGUID */
40
+ allowedAAGUIDs?: string[];
41
+ }
42
+ /**
43
+ * Create an Express Router with pre-built humankey routes.
44
+ *
45
+ * Routes:
46
+ * - POST /challenge — generate and store a challenge
47
+ * - POST /register — verify registration and store credential
48
+ * - POST /verify — verify a tap proof
49
+ */
50
+ declare function createHumanKeyRouter(config: HumanKeyExpressConfig): Router;
51
+
52
+ export { ActionPayload, type ChallengeStore, type HumanKeyExpressConfig, MemoryChallengeStore, TapCredential, VerifyResult, createHumanKeyRouter };
@@ -0,0 +1,52 @@
1
+ import { Router } from 'express';
2
+ import { c as TapCredential, V as VerifyResult, A as ActionPayload } from './types-By44RNIt.js';
3
+ import '@simplewebauthn/server';
4
+
5
+ /** Abstraction for challenge storage with TTL and single-use semantics. */
6
+ interface ChallengeStore {
7
+ /** Store a challenge with a TTL. */
8
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
9
+ /** Retrieve and delete a challenge (single-use). Returns null if expired or not found. */
10
+ get(id: string): Promise<string | null>;
11
+ }
12
+ /** In-memory challenge store with TTL enforcement. Use a database or Redis in production. */
13
+ declare class MemoryChallengeStore implements ChallengeStore {
14
+ private store;
15
+ set(id: string, challenge: string, ttlMs: number): Promise<void>;
16
+ get(id: string): Promise<string | null>;
17
+ private cleanup;
18
+ }
19
+ /** Configuration for the humankey Express router. */
20
+ interface HumanKeyExpressConfig {
21
+ /** Relying party ID, e.g. "example.com" */
22
+ rpID: string;
23
+ /** Relying party display name */
24
+ rpName: string;
25
+ /** Expected origin(s), e.g. "https://example.com" */
26
+ origin: string | string[];
27
+ /** Challenge TTL in ms (default: 60000) */
28
+ challengeTTL?: number;
29
+ /** Custom challenge store (default: MemoryChallengeStore) */
30
+ challengeStore?: ChallengeStore;
31
+ /** Look up a stored credential by ID */
32
+ getCredential: (credentialId: string) => Promise<TapCredential | null>;
33
+ /** Called after successful registration — store the credential */
34
+ onRegister: (credential: TapCredential) => Promise<void>;
35
+ /** Called after successful tap verification */
36
+ onVerify?: (result: VerifyResult, action: ActionPayload) => Promise<void>;
37
+ /** Require user verification (default: true) */
38
+ requireUserVerification?: boolean;
39
+ /** Restrict to specific authenticator models by AAGUID */
40
+ allowedAAGUIDs?: string[];
41
+ }
42
+ /**
43
+ * Create an Express Router with pre-built humankey routes.
44
+ *
45
+ * Routes:
46
+ * - POST /challenge — generate and store a challenge
47
+ * - POST /register — verify registration and store credential
48
+ * - POST /verify — verify a tap proof
49
+ */
50
+ declare function createHumanKeyRouter(config: HumanKeyExpressConfig): Router;
51
+
52
+ export { ActionPayload, type ChallengeStore, type HumanKeyExpressConfig, MemoryChallengeStore, TapCredential, VerifyResult, createHumanKeyRouter };
@@ -0,0 +1,124 @@
1
+ import {
2
+ createChallenge,
3
+ verifyRegistration,
4
+ verifyTapProof
5
+ } from "./chunk-XXJAJHNP.mjs";
6
+ import {
7
+ HumanKeyError
8
+ } from "./chunk-CLQSCDXC.mjs";
9
+
10
+ // src/express.ts
11
+ import { Router } from "express";
12
+ var MemoryChallengeStore = class {
13
+ store = /* @__PURE__ */ new Map();
14
+ async set(id, challenge, ttlMs) {
15
+ this.store.set(id, { challenge, expiresAt: Date.now() + ttlMs });
16
+ this.cleanup();
17
+ }
18
+ async get(id) {
19
+ const entry = this.store.get(id);
20
+ if (!entry) return null;
21
+ this.store.delete(id);
22
+ if (Date.now() > entry.expiresAt) return null;
23
+ return entry.challenge;
24
+ }
25
+ cleanup() {
26
+ const now = Date.now();
27
+ for (const [id, entry] of this.store) {
28
+ if (now > entry.expiresAt) this.store.delete(id);
29
+ }
30
+ }
31
+ };
32
+ function createHumanKeyRouter(config) {
33
+ const {
34
+ rpID,
35
+ origin,
36
+ challengeTTL = 6e4,
37
+ challengeStore = new MemoryChallengeStore(),
38
+ getCredential,
39
+ onRegister,
40
+ onVerify,
41
+ requireUserVerification = true,
42
+ allowedAAGUIDs
43
+ } = config;
44
+ const router = Router();
45
+ router.post("/challenge", async (_req, res) => {
46
+ try {
47
+ const challenge = createChallenge();
48
+ const challengeId = crypto.randomUUID();
49
+ await challengeStore.set(challengeId, challenge, challengeTTL);
50
+ res.json({ challengeId, challenge });
51
+ } catch (error) {
52
+ handleError(res, error);
53
+ }
54
+ });
55
+ router.post("/register", async (req, res) => {
56
+ try {
57
+ const { response, challengeId } = req.body;
58
+ const challenge = await challengeStore.get(challengeId);
59
+ if (!challenge) {
60
+ res.status(400).json({ error: "Challenge not found or expired" });
61
+ return;
62
+ }
63
+ const result = await verifyRegistration({
64
+ response,
65
+ expectedChallenge: challenge,
66
+ expectedOrigin: origin,
67
+ expectedRPID: rpID,
68
+ requireUserVerification,
69
+ allowedAAGUIDs
70
+ });
71
+ await onRegister(result.credential);
72
+ res.json({ ok: true, credentialId: result.credential.id });
73
+ } catch (error) {
74
+ handleError(res, error);
75
+ }
76
+ });
77
+ router.post("/verify", async (req, res) => {
78
+ try {
79
+ const { proof, challengeId, action } = req.body;
80
+ const challenge = await challengeStore.get(challengeId);
81
+ if (!challenge) {
82
+ res.status(400).json({ error: "Challenge not found or expired" });
83
+ return;
84
+ }
85
+ const credential = await getCredential(proof.response?.id);
86
+ if (!credential) {
87
+ res.status(400).json({ error: "Credential not found" });
88
+ return;
89
+ }
90
+ const result = await verifyTapProof({
91
+ proof,
92
+ credential,
93
+ expectedChallenge: challenge,
94
+ expectedAction: action,
95
+ expectedOrigin: origin,
96
+ expectedRPID: rpID,
97
+ requireUserVerification
98
+ });
99
+ credential.counter = result.newCounter;
100
+ if (onVerify) {
101
+ await onVerify(result, action);
102
+ }
103
+ res.json({
104
+ verified: result.verified,
105
+ confirmationValid: result.confirmationValid,
106
+ userVerified: result.userVerified
107
+ });
108
+ } catch (error) {
109
+ handleError(res, error);
110
+ }
111
+ });
112
+ return router;
113
+ }
114
+ function handleError(res, error) {
115
+ if (error instanceof HumanKeyError) {
116
+ res.status(400).json({ error: error.message, code: error.code });
117
+ } else {
118
+ res.status(500).json({ error: "Internal server error" });
119
+ }
120
+ }
121
+ export {
122
+ MemoryChallengeStore,
123
+ createHumanKeyRouter
124
+ };