humankey 0.3.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.
@@ -0,0 +1,75 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunk2KMBF2THcjs = require('./chunk-2KMBF2TH.cjs');
4
+
5
+
6
+
7
+
8
+
9
+
10
+ var _chunkJI6NIMGKcjs = require('./chunk-JI6NIMGK.cjs');
11
+
12
+ // src/tap.ts
13
+ var _browser = require('@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
+ _chunk2KMBF2THcjs.validateConfirmation.call(void 0, confirmation, userInput);
26
+ const actionHashBuffer = await _chunkJI6NIMGKcjs.hashAction.call(void 0, 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 = _chunkJI6NIMGKcjs.base64urlToBuffer.call(void 0, challenge);
33
+ const finalChallenge = await _chunkJI6NIMGKcjs.combineAndHash.call(void 0,
34
+ challengeBuffer,
35
+ actionHashBuffer,
36
+ confirmationHashBuffer
37
+ );
38
+ const actionHash = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, actionHashBuffer);
39
+ const confirmationHash = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, confirmationHashBuffer);
40
+ try {
41
+ const response = await _browser.startAuthentication.call(void 0, {
42
+ optionsJSON: {
43
+ challenge: _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, 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 (0, _chunkJI6NIMGKcjs.HumanKeyError)(
64
+ "User cancelled the hardware key tap",
65
+ "USER_CANCELLED",
66
+ error
67
+ );
68
+ }
69
+ throw error;
70
+ }
71
+ }
72
+
73
+
74
+
75
+ exports.requestTap = requestTap;
@@ -0,0 +1,76 @@
1
+ import {
2
+ HumanKeyError,
3
+ bufferToBase64url
4
+ } from "./chunk-CLQSCDXC.mjs";
5
+
6
+ // src/register.ts
7
+ import { startRegistration } from "@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 = bufferToBase64url(userIdHash);
23
+ try {
24
+ const response = await startRegistration({
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 HumanKeyError(
61
+ "User cancelled hardware key registration",
62
+ "USER_CANCELLED",
63
+ error
64
+ );
65
+ }
66
+ throw new HumanKeyError(
67
+ `Registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
68
+ "REGISTRATION_FAILED",
69
+ error
70
+ );
71
+ }
72
+ }
73
+
74
+ export {
75
+ registerKey
76
+ };
@@ -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/adapter-core.ts
11
+ var MemoryChallengeStore = class {
12
+ 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
+ };
31
+ function resolveConfig(config) {
32
+ return {
33
+ rpID: config.rpID,
34
+ rpName: config.rpName,
35
+ origin: config.origin,
36
+ challengeTTL: config.challengeTTL ?? 6e4,
37
+ challengeStore: config.challengeStore ?? new MemoryChallengeStore(),
38
+ getCredential: config.getCredential,
39
+ onRegister: config.onRegister,
40
+ onVerify: config.onVerify,
41
+ requireUserVerification: config.requireUserVerification ?? true,
42
+ allowedAAGUIDs: config.allowedAAGUIDs
43
+ };
44
+ }
45
+ async function handleChallenge(config) {
46
+ try {
47
+ const challenge = createChallenge();
48
+ const challengeId = crypto.randomUUID();
49
+ await config.challengeStore.set(challengeId, challenge, config.challengeTTL);
50
+ return { status: 200, body: { challengeId, challenge } };
51
+ } catch {
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 verifyRegistration({
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(proof.response?.id);
83
+ if (!credential) {
84
+ return { status: 400, body: { error: "Credential not found" } };
85
+ }
86
+ const result = await verifyTapProof({
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 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
+ export {
119
+ MemoryChallengeStore,
120
+ resolveConfig,
121
+ handleChallenge,
122
+ handleRegister,
123
+ handleVerify
124
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+ var _chunk2KMBF2THcjs = require('./chunk-2KMBF2TH.cjs');
5
+ require('./chunk-JI6NIMGK.cjs');
6
+
7
+
8
+
9
+ exports.createConfirmation = _chunk2KMBF2THcjs.createConfirmation; exports.validateConfirmation = _chunk2KMBF2THcjs.validateConfirmation;
@@ -0,0 +1,9 @@
1
+ import {
2
+ createConfirmation,
3
+ validateConfirmation
4
+ } from "./chunk-G2X46ZRM.mjs";
5
+ import "./chunk-CLQSCDXC.mjs";
6
+ export {
7
+ createConfirmation,
8
+ validateConfirmation
9
+ };
package/dist/express.cjs CHANGED
@@ -1,124 +1,36 @@
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;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
4
 
5
- var _chunkFW3YUVJCcjs = require('./chunk-FW3YUVJC.cjs');
6
5
 
7
6
 
8
- var _chunkJI6NIMGKcjs = require('./chunk-JI6NIMGK.cjs');
7
+ var _chunkG2IT2ZP5cjs = require('./chunk-G2IT2ZP5.cjs');
8
+ require('./chunk-FW3YUVJC.cjs');
9
+ require('./chunk-JI6NIMGK.cjs');
9
10
 
10
11
  // src/express.ts
11
12
  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
13
  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;
14
+ const resolved = _chunkG2IT2ZP5cjs.resolveConfig.call(void 0, config);
44
15
  const router = _express.Router.call(void 0, );
45
16
  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
- }
17
+ const result = await _chunkG2IT2ZP5cjs.handleChallenge.call(void 0, resolved);
18
+ sendResult(res, result);
54
19
  });
55
20
  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
- }
21
+ const result = await _chunkG2IT2ZP5cjs.handleRegister.call(void 0, resolved, req.body);
22
+ sendResult(res, result);
76
23
  });
77
24
  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
- }
25
+ const result = await _chunkG2IT2ZP5cjs.handleVerify.call(void 0, resolved, req.body);
26
+ sendResult(res, result);
111
27
  });
112
28
  return router;
113
29
  }
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
- }
30
+ function sendResult(res, result) {
31
+ res.status(result.status).json(result.body);
120
32
  }
121
33
 
122
34
 
123
35
 
124
- exports.MemoryChallengeStore = MemoryChallengeStore; exports.createHumanKeyRouter = createHumanKeyRouter;
36
+ exports.MemoryChallengeStore = _chunkG2IT2ZP5cjs.MemoryChallengeStore; exports.createHumanKeyRouter = createHumanKeyRouter;
@@ -1,44 +1,11 @@
1
1
  import { Router } from 'express';
2
- import { c as TapCredential, V as VerifyResult, A as ActionPayload } from './types-By44RNIt.cjs';
2
+ import { H as HumanKeyAdapterConfig } from './adapter-core-CoBcSqdG.cjs';
3
+ export { C as ChallengeStore, M as MemoryChallengeStore } from './adapter-core-CoBcSqdG.cjs';
4
+ export { A as ActionPayload, c as TapCredential, V as VerifyResult } from './types-By44RNIt.cjs';
3
5
  import '@simplewebauthn/server';
4
6
 
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
7
  /** 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
- }
8
+ type HumanKeyExpressConfig = HumanKeyAdapterConfig;
42
9
  /**
43
10
  * Create an Express Router with pre-built humankey routes.
44
11
  *
@@ -49,4 +16,4 @@ interface HumanKeyExpressConfig {
49
16
  */
50
17
  declare function createHumanKeyRouter(config: HumanKeyExpressConfig): Router;
51
18
 
52
- export { ActionPayload, type ChallengeStore, type HumanKeyExpressConfig, MemoryChallengeStore, TapCredential, VerifyResult, createHumanKeyRouter };
19
+ export { type HumanKeyExpressConfig, createHumanKeyRouter };
package/dist/express.d.ts CHANGED
@@ -1,44 +1,11 @@
1
1
  import { Router } from 'express';
2
- import { c as TapCredential, V as VerifyResult, A as ActionPayload } from './types-By44RNIt.js';
2
+ import { H as HumanKeyAdapterConfig } from './adapter-core-CFN3y3L0.js';
3
+ export { C as ChallengeStore, M as MemoryChallengeStore } from './adapter-core-CFN3y3L0.js';
4
+ export { A as ActionPayload, c as TapCredential, V as VerifyResult } from './types-By44RNIt.js';
3
5
  import '@simplewebauthn/server';
4
6
 
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
7
  /** 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
- }
8
+ type HumanKeyExpressConfig = HumanKeyAdapterConfig;
42
9
  /**
43
10
  * Create an Express Router with pre-built humankey routes.
44
11
  *
@@ -49,4 +16,4 @@ interface HumanKeyExpressConfig {
49
16
  */
50
17
  declare function createHumanKeyRouter(config: HumanKeyExpressConfig): Router;
51
18
 
52
- export { ActionPayload, type ChallengeStore, type HumanKeyExpressConfig, MemoryChallengeStore, TapCredential, VerifyResult, createHumanKeyRouter };
19
+ export { type HumanKeyExpressConfig, createHumanKeyRouter };
package/dist/express.mjs CHANGED
@@ -1,122 +1,34 @@
1
1
  import {
2
- createChallenge,
3
- verifyRegistration,
4
- verifyTapProof
5
- } from "./chunk-XXJAJHNP.mjs";
6
- import {
7
- HumanKeyError
8
- } from "./chunk-CLQSCDXC.mjs";
2
+ MemoryChallengeStore,
3
+ handleChallenge,
4
+ handleRegister,
5
+ handleVerify,
6
+ resolveConfig
7
+ } from "./chunk-Y6KQC7WK.mjs";
8
+ import "./chunk-XXJAJHNP.mjs";
9
+ import "./chunk-CLQSCDXC.mjs";
9
10
 
10
11
  // src/express.ts
11
12
  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
13
  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;
14
+ const resolved = resolveConfig(config);
44
15
  const router = Router();
45
16
  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
- }
17
+ const result = await handleChallenge(resolved);
18
+ sendResult(res, result);
54
19
  });
55
20
  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
- }
21
+ const result = await handleRegister(resolved, req.body);
22
+ sendResult(res, result);
76
23
  });
77
24
  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
- }
25
+ const result = await handleVerify(resolved, req.body);
26
+ sendResult(res, result);
111
27
  });
112
28
  return router;
113
29
  }
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
- }
30
+ function sendResult(res, result) {
31
+ res.status(result.status).json(result.body);
120
32
  }
121
33
  export {
122
34
  MemoryChallengeStore,