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.
package/dist/index.cjs ADDED
@@ -0,0 +1,167 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+ var _chunkJI6NIMGKcjs = require('./chunk-JI6NIMGK.cjs');
9
+
10
+ // src/confirm.ts
11
+ async function createConfirmation(action) {
12
+ const hashBuffer = await _chunkJI6NIMGKcjs.hashAction.call(void 0, action);
13
+ const code = _chunkJI6NIMGKcjs.deriveConfirmationCode.call(void 0, hashBuffer);
14
+ const actionHash = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, hashBuffer);
15
+ return { code, actionHash };
16
+ }
17
+ function validateConfirmation(confirmation, userInput) {
18
+ if (userInput.toUpperCase().trim() !== confirmation.code.toUpperCase()) {
19
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
20
+ "Confirmation code mismatch",
21
+ "CONFIRMATION_MISMATCH"
22
+ );
23
+ }
24
+ }
25
+
26
+ // src/tap.ts
27
+ var _browser = require('@simplewebauthn/browser');
28
+ async function requestTap(request) {
29
+ const {
30
+ challenge,
31
+ action,
32
+ confirmation,
33
+ userInput,
34
+ allowCredentials,
35
+ rpID,
36
+ userVerification = "required",
37
+ timeout = 6e4
38
+ } = request;
39
+ validateConfirmation(confirmation, userInput);
40
+ const actionHashBuffer = await _chunkJI6NIMGKcjs.hashAction.call(void 0, action);
41
+ const confirmationInput = `${confirmation.code}:${userInput.toUpperCase().trim()}`;
42
+ const confirmationHashBuffer = await crypto.subtle.digest(
43
+ "SHA-256",
44
+ new TextEncoder().encode(confirmationInput)
45
+ );
46
+ const challengeBuffer = _chunkJI6NIMGKcjs.base64urlToBuffer.call(void 0, challenge);
47
+ const finalChallenge = await _chunkJI6NIMGKcjs.combineAndHash.call(void 0,
48
+ challengeBuffer,
49
+ actionHashBuffer,
50
+ confirmationHashBuffer
51
+ );
52
+ const actionHash = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, actionHashBuffer);
53
+ const confirmationHash = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, confirmationHashBuffer);
54
+ try {
55
+ const response = await _browser.startAuthentication.call(void 0, {
56
+ optionsJSON: {
57
+ challenge: _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, finalChallenge),
58
+ rpId: rpID,
59
+ allowCredentials: allowCredentials.map((cred) => ({
60
+ id: cred.id,
61
+ type: "public-key",
62
+ transports: cred.transports
63
+ })),
64
+ userVerification,
65
+ timeout
66
+ }
67
+ });
68
+ return {
69
+ response,
70
+ action,
71
+ actionHash,
72
+ confirmationHash,
73
+ userInput: userInput.toUpperCase().trim()
74
+ };
75
+ } catch (error) {
76
+ if (error instanceof Error && error.name === "NotAllowedError") {
77
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
78
+ "User cancelled the hardware key tap",
79
+ "USER_CANCELLED",
80
+ error
81
+ );
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ // src/register.ts
88
+
89
+ async function registerKey(request) {
90
+ const {
91
+ challenge,
92
+ rpID,
93
+ rpName,
94
+ userName,
95
+ userDisplayName = userName,
96
+ excludeCredentials = [],
97
+ attestation = "direct",
98
+ userVerification = "required",
99
+ timeout = 6e4
100
+ } = request;
101
+ const userIdBytes = new TextEncoder().encode(userName);
102
+ const userIdHash = await crypto.subtle.digest("SHA-256", userIdBytes);
103
+ const userId = _chunkJI6NIMGKcjs.bufferToBase64url.call(void 0, userIdHash);
104
+ try {
105
+ const response = await _browser.startRegistration.call(void 0, {
106
+ optionsJSON: {
107
+ rp: { name: rpName, id: rpID },
108
+ user: {
109
+ id: userId,
110
+ name: userName,
111
+ displayName: userDisplayName
112
+ },
113
+ challenge,
114
+ pubKeyCredParams: [
115
+ { alg: -7, type: "public-key" },
116
+ // ES256
117
+ { alg: -257, type: "public-key" }
118
+ // RS256
119
+ ],
120
+ timeout,
121
+ attestation,
122
+ excludeCredentials: excludeCredentials.map((cred) => ({
123
+ id: cred.id,
124
+ type: "public-key",
125
+ transports: cred.transports
126
+ })),
127
+ authenticatorSelection: {
128
+ authenticatorAttachment: "cross-platform",
129
+ residentKey: "preferred",
130
+ userVerification
131
+ }
132
+ }
133
+ });
134
+ return {
135
+ credentialId: response.id,
136
+ response,
137
+ transports: response.response.transports
138
+ };
139
+ } catch (error) {
140
+ if (error instanceof Error && error.name === "NotAllowedError") {
141
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
142
+ "User cancelled hardware key registration",
143
+ "USER_CANCELLED",
144
+ error
145
+ );
146
+ }
147
+ throw new (0, _chunkJI6NIMGKcjs.HumanKeyError)(
148
+ `Registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
149
+ "REGISTRATION_FAILED",
150
+ error
151
+ );
152
+ }
153
+ }
154
+
155
+ // src/support.ts
156
+ function isHumanKeySupported() {
157
+ return typeof window !== "undefined" && typeof window.PublicKeyCredential !== "undefined" && typeof navigator.credentials !== "undefined";
158
+ }
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+
167
+ exports.HumanKeyError = _chunkJI6NIMGKcjs.HumanKeyError; exports.createConfirmation = createConfirmation; exports.hashAction = _chunkJI6NIMGKcjs.hashAction; exports.isHumanKeySupported = isHumanKeySupported; exports.registerKey = registerKey; exports.requestTap = requestTap; exports.validateConfirmation = validateConfirmation;
@@ -0,0 +1,52 @@
1
+ import { A as ActionPayload, C as Confirmation, T as TapRequest, a as TapProof, R as RegisterKeyRequest, b as RegistrationResult } from './types-By44RNIt.cjs';
2
+ export { c as TapCredential, V as VerifyResult, d as VerifyTapProofRequest } from './types-By44RNIt.cjs';
3
+ export { H as HumanKeyError, a as HumanKeyErrorCode } from './errors-BmtCXo7w.cjs';
4
+ import '@simplewebauthn/server';
5
+
6
+ /**
7
+ * Generate a confirmation challenge for an action.
8
+ * Returns a Confirmation containing a 4-char code derived from the action hash.
9
+ * The developer shows this code in their UI and asks the user to type it back.
10
+ */
11
+ declare function createConfirmation(action: ActionPayload): Promise<Confirmation>;
12
+ /**
13
+ * Validate that the user's input matches the expected confirmation code.
14
+ * Case-insensitive comparison.
15
+ * Throws CONFIRMATION_MISMATCH if the input doesn't match.
16
+ */
17
+ declare function validateConfirmation(confirmation: Confirmation, userInput: string): void;
18
+
19
+ /**
20
+ * Request a hardware key tap to approve an action.
21
+ * Requires a valid confirmation (user must have typed the correct code).
22
+ *
23
+ * Flow:
24
+ * 1. Validates the confirmation code
25
+ * 2. Hashes the action + confirmation into the WebAuthn challenge
26
+ * 3. Prompts the user to tap their hardware key
27
+ * 4. Returns a TapProof containing the signed assertion
28
+ */
29
+ declare function requestTap(request: TapRequest): Promise<TapProof>;
30
+
31
+ /**
32
+ * Register a new hardware key for per-action verification.
33
+ * Defaults to cross-platform authenticators (YubiKey, not Touch ID)
34
+ * and direct attestation (verifies real hardware).
35
+ */
36
+ declare function registerKey(request: RegisterKeyRequest): Promise<RegistrationResult>;
37
+
38
+ /**
39
+ * Check if WebAuthn is supported in this browser.
40
+ * Returns true if the browser supports the credential management API
41
+ * needed for hardware key authentication.
42
+ */
43
+ declare function isHumanKeySupported(): boolean;
44
+
45
+ /**
46
+ * SHA-256 hash of canonicalized action JSON.
47
+ * Canonical form: keys sorted alphabetically, no whitespace.
48
+ * Works in both browser and Node.js (via globalThis.crypto).
49
+ */
50
+ declare function hashAction(action: ActionPayload): Promise<ArrayBuffer>;
51
+
52
+ export { ActionPayload, Confirmation, RegisterKeyRequest, RegistrationResult, TapProof, TapRequest, createConfirmation, hashAction, isHumanKeySupported, registerKey, requestTap, validateConfirmation };
@@ -0,0 +1,52 @@
1
+ import { A as ActionPayload, C as Confirmation, T as TapRequest, a as TapProof, R as RegisterKeyRequest, b as RegistrationResult } from './types-By44RNIt.js';
2
+ export { c as TapCredential, V as VerifyResult, d as VerifyTapProofRequest } from './types-By44RNIt.js';
3
+ export { H as HumanKeyError, a as HumanKeyErrorCode } from './errors-BmtCXo7w.js';
4
+ import '@simplewebauthn/server';
5
+
6
+ /**
7
+ * Generate a confirmation challenge for an action.
8
+ * Returns a Confirmation containing a 4-char code derived from the action hash.
9
+ * The developer shows this code in their UI and asks the user to type it back.
10
+ */
11
+ declare function createConfirmation(action: ActionPayload): Promise<Confirmation>;
12
+ /**
13
+ * Validate that the user's input matches the expected confirmation code.
14
+ * Case-insensitive comparison.
15
+ * Throws CONFIRMATION_MISMATCH if the input doesn't match.
16
+ */
17
+ declare function validateConfirmation(confirmation: Confirmation, userInput: string): void;
18
+
19
+ /**
20
+ * Request a hardware key tap to approve an action.
21
+ * Requires a valid confirmation (user must have typed the correct code).
22
+ *
23
+ * Flow:
24
+ * 1. Validates the confirmation code
25
+ * 2. Hashes the action + confirmation into the WebAuthn challenge
26
+ * 3. Prompts the user to tap their hardware key
27
+ * 4. Returns a TapProof containing the signed assertion
28
+ */
29
+ declare function requestTap(request: TapRequest): Promise<TapProof>;
30
+
31
+ /**
32
+ * Register a new hardware key for per-action verification.
33
+ * Defaults to cross-platform authenticators (YubiKey, not Touch ID)
34
+ * and direct attestation (verifies real hardware).
35
+ */
36
+ declare function registerKey(request: RegisterKeyRequest): Promise<RegistrationResult>;
37
+
38
+ /**
39
+ * Check if WebAuthn is supported in this browser.
40
+ * Returns true if the browser supports the credential management API
41
+ * needed for hardware key authentication.
42
+ */
43
+ declare function isHumanKeySupported(): boolean;
44
+
45
+ /**
46
+ * SHA-256 hash of canonicalized action JSON.
47
+ * Canonical form: keys sorted alphabetically, no whitespace.
48
+ * Works in both browser and Node.js (via globalThis.crypto).
49
+ */
50
+ declare function hashAction(action: ActionPayload): Promise<ArrayBuffer>;
51
+
52
+ export { ActionPayload, Confirmation, RegisterKeyRequest, RegistrationResult, TapProof, TapRequest, createConfirmation, hashAction, isHumanKeySupported, registerKey, requestTap, validateConfirmation };
package/dist/index.mjs ADDED
@@ -0,0 +1,167 @@
1
+ import {
2
+ HumanKeyError,
3
+ base64urlToBuffer,
4
+ bufferToBase64url,
5
+ combineAndHash,
6
+ deriveConfirmationCode,
7
+ hashAction
8
+ } from "./chunk-CLQSCDXC.mjs";
9
+
10
+ // src/confirm.ts
11
+ async function createConfirmation(action) {
12
+ const hashBuffer = await hashAction(action);
13
+ const code = deriveConfirmationCode(hashBuffer);
14
+ const actionHash = bufferToBase64url(hashBuffer);
15
+ return { code, actionHash };
16
+ }
17
+ function validateConfirmation(confirmation, userInput) {
18
+ if (userInput.toUpperCase().trim() !== confirmation.code.toUpperCase()) {
19
+ throw new HumanKeyError(
20
+ "Confirmation code mismatch",
21
+ "CONFIRMATION_MISMATCH"
22
+ );
23
+ }
24
+ }
25
+
26
+ // src/tap.ts
27
+ import { startAuthentication } from "@simplewebauthn/browser";
28
+ async function requestTap(request) {
29
+ const {
30
+ challenge,
31
+ action,
32
+ confirmation,
33
+ userInput,
34
+ allowCredentials,
35
+ rpID,
36
+ userVerification = "required",
37
+ timeout = 6e4
38
+ } = request;
39
+ validateConfirmation(confirmation, userInput);
40
+ const actionHashBuffer = await hashAction(action);
41
+ const confirmationInput = `${confirmation.code}:${userInput.toUpperCase().trim()}`;
42
+ const confirmationHashBuffer = await crypto.subtle.digest(
43
+ "SHA-256",
44
+ new TextEncoder().encode(confirmationInput)
45
+ );
46
+ const challengeBuffer = base64urlToBuffer(challenge);
47
+ const finalChallenge = await combineAndHash(
48
+ challengeBuffer,
49
+ actionHashBuffer,
50
+ confirmationHashBuffer
51
+ );
52
+ const actionHash = bufferToBase64url(actionHashBuffer);
53
+ const confirmationHash = bufferToBase64url(confirmationHashBuffer);
54
+ try {
55
+ const response = await startAuthentication({
56
+ optionsJSON: {
57
+ challenge: bufferToBase64url(finalChallenge),
58
+ rpId: rpID,
59
+ allowCredentials: allowCredentials.map((cred) => ({
60
+ id: cred.id,
61
+ type: "public-key",
62
+ transports: cred.transports
63
+ })),
64
+ userVerification,
65
+ timeout
66
+ }
67
+ });
68
+ return {
69
+ response,
70
+ action,
71
+ actionHash,
72
+ confirmationHash,
73
+ userInput: userInput.toUpperCase().trim()
74
+ };
75
+ } catch (error) {
76
+ if (error instanceof Error && error.name === "NotAllowedError") {
77
+ throw new HumanKeyError(
78
+ "User cancelled the hardware key tap",
79
+ "USER_CANCELLED",
80
+ error
81
+ );
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ // src/register.ts
88
+ import { startRegistration } from "@simplewebauthn/browser";
89
+ async function registerKey(request) {
90
+ const {
91
+ challenge,
92
+ rpID,
93
+ rpName,
94
+ userName,
95
+ userDisplayName = userName,
96
+ excludeCredentials = [],
97
+ attestation = "direct",
98
+ userVerification = "required",
99
+ timeout = 6e4
100
+ } = request;
101
+ const userIdBytes = new TextEncoder().encode(userName);
102
+ const userIdHash = await crypto.subtle.digest("SHA-256", userIdBytes);
103
+ const userId = bufferToBase64url(userIdHash);
104
+ try {
105
+ const response = await startRegistration({
106
+ optionsJSON: {
107
+ rp: { name: rpName, id: rpID },
108
+ user: {
109
+ id: userId,
110
+ name: userName,
111
+ displayName: userDisplayName
112
+ },
113
+ challenge,
114
+ pubKeyCredParams: [
115
+ { alg: -7, type: "public-key" },
116
+ // ES256
117
+ { alg: -257, type: "public-key" }
118
+ // RS256
119
+ ],
120
+ timeout,
121
+ attestation,
122
+ excludeCredentials: excludeCredentials.map((cred) => ({
123
+ id: cred.id,
124
+ type: "public-key",
125
+ transports: cred.transports
126
+ })),
127
+ authenticatorSelection: {
128
+ authenticatorAttachment: "cross-platform",
129
+ residentKey: "preferred",
130
+ userVerification
131
+ }
132
+ }
133
+ });
134
+ return {
135
+ credentialId: response.id,
136
+ response,
137
+ transports: response.response.transports
138
+ };
139
+ } catch (error) {
140
+ if (error instanceof Error && error.name === "NotAllowedError") {
141
+ throw new HumanKeyError(
142
+ "User cancelled hardware key registration",
143
+ "USER_CANCELLED",
144
+ error
145
+ );
146
+ }
147
+ throw new HumanKeyError(
148
+ `Registration failed: ${error instanceof Error ? error.message : "Unknown error"}`,
149
+ "REGISTRATION_FAILED",
150
+ error
151
+ );
152
+ }
153
+ }
154
+
155
+ // src/support.ts
156
+ function isHumanKeySupported() {
157
+ return typeof window !== "undefined" && typeof window.PublicKeyCredential !== "undefined" && typeof navigator.credentials !== "undefined";
158
+ }
159
+ export {
160
+ HumanKeyError,
161
+ createConfirmation,
162
+ hashAction,
163
+ isHumanKeySupported,
164
+ registerKey,
165
+ requestTap,
166
+ validateConfirmation
167
+ };
@@ -0,0 +1,134 @@
1
+ import { AuthenticatorTransportFuture, RegistrationResponseJSON, CredentialDeviceType, AuthenticationResponseJSON } from '@simplewebauthn/server';
2
+
3
+ /** The action the user is approving */
4
+ interface ActionPayload {
5
+ /** Unique action type identifier, e.g. "send-message", "approve-transfer" */
6
+ action: string;
7
+ /** Arbitrary data that gets hashed into the challenge. Supports nested objects and arrays. */
8
+ data: Record<string, unknown>;
9
+ }
10
+ /** Output of createConfirmation() — contains the code the user must type */
11
+ interface Confirmation {
12
+ /** 4-character alphanumeric code derived from the action hash */
13
+ code: string;
14
+ /** The SHA-256 hash of the canonical action JSON (base64url) */
15
+ actionHash: string;
16
+ }
17
+ /** Input for requestTap() */
18
+ interface TapRequest {
19
+ /** Server-generated challenge (base64url) */
20
+ challenge: string;
21
+ /** The action being approved */
22
+ action: ActionPayload;
23
+ /** Confirmation from createConfirmation() */
24
+ confirmation: Confirmation;
25
+ /** What the user actually typed as their confirmation code */
26
+ userInput: string;
27
+ /** Credential IDs the user can authenticate with */
28
+ allowCredentials: Array<{
29
+ id: string;
30
+ transports?: AuthenticatorTransportFuture[];
31
+ }>;
32
+ /** Relying party ID, e.g. "example.com" */
33
+ rpID: string;
34
+ /** User verification requirement (default: 'required') */
35
+ userVerification?: UserVerificationRequirement;
36
+ /** Timeout in ms (default: 60000) */
37
+ timeout?: number;
38
+ }
39
+ /** The signed proof returned by requestTap() — send this to your server */
40
+ interface TapProof {
41
+ /** The raw WebAuthn authentication response */
42
+ response: AuthenticationResponseJSON;
43
+ /** The action that was approved */
44
+ action: ActionPayload;
45
+ /** The action hash that was signed (base64url) */
46
+ actionHash: string;
47
+ /** The confirmation hash that was signed (base64url) */
48
+ confirmationHash: string;
49
+ /** The user's confirmation input */
50
+ userInput: string;
51
+ }
52
+ /** Input for registerKey() */
53
+ interface RegisterKeyRequest {
54
+ /** Server-generated challenge (base64url) */
55
+ challenge: string;
56
+ /** Relying party ID */
57
+ rpID: string;
58
+ /** Relying party display name */
59
+ rpName: string;
60
+ /** Username for this credential */
61
+ userName: string;
62
+ /** Display name (defaults to userName) */
63
+ userDisplayName?: string;
64
+ /** Credential IDs to exclude (already registered) */
65
+ excludeCredentials?: Array<{
66
+ id: string;
67
+ transports?: AuthenticatorTransportFuture[];
68
+ }>;
69
+ /** Attestation type (default: 'direct' — verifies real hardware key) */
70
+ attestation?: AttestationConveyancePreference;
71
+ /** User verification requirement (default: 'required') */
72
+ userVerification?: UserVerificationRequirement;
73
+ /** Timeout in ms (default: 60000) */
74
+ timeout?: number;
75
+ }
76
+ /** A registered credential — store this server-side */
77
+ interface TapCredential {
78
+ /** Base64url credential ID */
79
+ id: string;
80
+ /** The credential public key bytes */
81
+ publicKey: Uint8Array;
82
+ /** Signature counter for replay detection */
83
+ counter: number;
84
+ /** Transports the authenticator supports */
85
+ transports?: AuthenticatorTransportFuture[];
86
+ /** Single-device or multi-device credential */
87
+ deviceType: CredentialDeviceType;
88
+ /** Whether the credential is backed up */
89
+ backedUp: boolean;
90
+ /** Authenticator AAGUID — identifies the make/model of the hardware key */
91
+ aaguid: string;
92
+ }
93
+ /** Result of registerKey() — send the response to your server for verification */
94
+ interface RegistrationResult {
95
+ /** Base64url credential ID */
96
+ credentialId: string;
97
+ /** The raw registration response — send to your server for verifyRegistration() */
98
+ response: RegistrationResponseJSON;
99
+ /** Transports the authenticator supports */
100
+ transports?: AuthenticatorTransportFuture[];
101
+ }
102
+ /** Input for verifyTapProof() */
103
+ interface VerifyTapProofRequest {
104
+ /** The TapProof from the client */
105
+ proof: TapProof;
106
+ /** The stored credential for this user */
107
+ credential: TapCredential;
108
+ /** The challenge you generated server-side (base64url) */
109
+ expectedChallenge: string;
110
+ /** Your server's copy of the action (used to re-derive hashes) */
111
+ expectedAction: ActionPayload;
112
+ /** Expected origin(s), e.g. "https://example.com" */
113
+ expectedOrigin: string | string[];
114
+ /** Expected relying party ID */
115
+ expectedRPID: string;
116
+ /** Require user verification (default: true) */
117
+ requireUserVerification?: boolean;
118
+ /** When true (default), throw CONFIRMATION_MISMATCH if the user typed the wrong code.
119
+ * Set to false to handle confirmation validation manually via result.confirmationValid. */
120
+ requireConfirmation?: boolean;
121
+ }
122
+ /** Result of verifyTapProof() */
123
+ interface VerifyResult {
124
+ /** Overall verification passed */
125
+ verified: boolean;
126
+ /** User verification (biometric/PIN) was performed */
127
+ userVerified: boolean;
128
+ /** User typed the correct confirmation code for this action */
129
+ confirmationValid: boolean;
130
+ /** Updated signature counter — store this */
131
+ newCounter: number;
132
+ }
133
+
134
+ export type { ActionPayload as A, Confirmation as C, RegisterKeyRequest as R, TapRequest as T, VerifyResult as V, TapProof as a, RegistrationResult as b, TapCredential as c, VerifyTapProofRequest as d };