unotoken 0.1.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/README.md +360 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1207 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +15 -0
- package/dist/client.js.map +1 -0
- package/dist/db.d.ts +52 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +97 -0
- package/dist/db.js.map +1 -0
- package/dist/dotenv.d.ts +69 -0
- package/dist/dotenv.d.ts.map +1 -0
- package/dist/dotenv.js +115 -0
- package/dist/dotenv.js.map +1 -0
- package/dist/env-mapper.d.ts +55 -0
- package/dist/env-mapper.d.ts.map +1 -0
- package/dist/env-mapper.js +97 -0
- package/dist/env-mapper.js.map +1 -0
- package/dist/exec.d.ts +80 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +214 -0
- package/dist/exec.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/commands.d.ts +151 -0
- package/dist/oauth/commands.d.ts.map +1 -0
- package/dist/oauth/commands.js +322 -0
- package/dist/oauth/commands.js.map +1 -0
- package/dist/oauth/config.d.ts +84 -0
- package/dist/oauth/config.d.ts.map +1 -0
- package/dist/oauth/config.js +156 -0
- package/dist/oauth/config.js.map +1 -0
- package/dist/oauth/crypto-helpers.d.ts +44 -0
- package/dist/oauth/crypto-helpers.d.ts.map +1 -0
- package/dist/oauth/crypto-helpers.js +94 -0
- package/dist/oauth/crypto-helpers.js.map +1 -0
- package/dist/oauth/device-secret.d.ts +57 -0
- package/dist/oauth/device-secret.d.ts.map +1 -0
- package/dist/oauth/device-secret.js +106 -0
- package/dist/oauth/device-secret.js.map +1 -0
- package/dist/oauth/flow.d.ts +112 -0
- package/dist/oauth/flow.d.ts.map +1 -0
- package/dist/oauth/flow.js +255 -0
- package/dist/oauth/flow.js.map +1 -0
- package/dist/oauth/index.d.ts +18 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +24 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/key-wrap.d.ts +146 -0
- package/dist/oauth/key-wrap.d.ts.map +1 -0
- package/dist/oauth/key-wrap.js +275 -0
- package/dist/oauth/key-wrap.js.map +1 -0
- package/dist/oauth/pkce.d.ts +29 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +34 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/provider.d.ts +79 -0
- package/dist/oauth/provider.d.ts.map +1 -0
- package/dist/oauth/provider.js +10 -0
- package/dist/oauth/provider.js.map +1 -0
- package/dist/oauth/providers/github.d.ts +75 -0
- package/dist/oauth/providers/github.d.ts.map +1 -0
- package/dist/oauth/providers/github.js +119 -0
- package/dist/oauth/providers/github.js.map +1 -0
- package/dist/oauth/providers/google.d.ts +115 -0
- package/dist/oauth/providers/google.d.ts.map +1 -0
- package/dist/oauth/providers/google.js +285 -0
- package/dist/oauth/providers/google.js.map +1 -0
- package/dist/sdk.d.ts +8 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +8 -0
- package/dist/sdk.js.map +1 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +287 -0
- package/dist/server.js.map +1 -0
- package/dist/signatures/approval-codes.d.ts +192 -0
- package/dist/signatures/approval-codes.d.ts.map +1 -0
- package/dist/signatures/approval-codes.js +407 -0
- package/dist/signatures/approval-codes.js.map +1 -0
- package/dist/signatures/commands.d.ts +108 -0
- package/dist/signatures/commands.d.ts.map +1 -0
- package/dist/signatures/commands.js +270 -0
- package/dist/signatures/commands.js.map +1 -0
- package/dist/signatures/devices.d.ts +165 -0
- package/dist/signatures/devices.d.ts.map +1 -0
- package/dist/signatures/devices.js +344 -0
- package/dist/signatures/devices.js.map +1 -0
- package/dist/signatures/email-config.d.ts +102 -0
- package/dist/signatures/email-config.d.ts.map +1 -0
- package/dist/signatures/email-config.js +188 -0
- package/dist/signatures/email-config.js.map +1 -0
- package/dist/signatures/email.d.ts +106 -0
- package/dist/signatures/email.d.ts.map +1 -0
- package/dist/signatures/email.js +180 -0
- package/dist/signatures/email.js.map +1 -0
- package/dist/signatures/fingerprint.d.ts +70 -0
- package/dist/signatures/fingerprint.d.ts.map +1 -0
- package/dist/signatures/fingerprint.js +123 -0
- package/dist/signatures/fingerprint.js.map +1 -0
- package/dist/signatures/guard.d.ts +118 -0
- package/dist/signatures/guard.d.ts.map +1 -0
- package/dist/signatures/guard.js +310 -0
- package/dist/signatures/guard.js.map +1 -0
- package/dist/signatures/resend.d.ts +84 -0
- package/dist/signatures/resend.d.ts.map +1 -0
- package/dist/signatures/resend.js +248 -0
- package/dist/signatures/resend.js.map +1 -0
- package/dist/token-requests.d.ts +80 -0
- package/dist/token-requests.d.ts.map +1 -0
- package/dist/token-requests.js +201 -0
- package/dist/token-requests.js.map +1 -0
- package/dist/tokens.d.ts +80 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +150 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email verification flow for unotoken device signatures.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the email verification process:
|
|
5
|
+
* 1. User provides their email address
|
|
6
|
+
* 2. A 6-digit code is generated and sent via Resend
|
|
7
|
+
* 3. User enters the code to verify ownership
|
|
8
|
+
* 4. On success, email is saved to email-config.json as verified
|
|
9
|
+
*
|
|
10
|
+
* This module connects email-config (persistence) and resend (sending)
|
|
11
|
+
* into a cohesive verification flow.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { type EmailConfig } from './email-config.js';
|
|
16
|
+
export interface PendingVerification {
|
|
17
|
+
/** The email address being verified */
|
|
18
|
+
email: string;
|
|
19
|
+
/** SHA-256 hash of the code (never store plaintext) */
|
|
20
|
+
codeHash: string;
|
|
21
|
+
/** When the code was generated (ISO 8601) */
|
|
22
|
+
createdAt: string;
|
|
23
|
+
/** When the code expires (ISO 8601) */
|
|
24
|
+
expiresAt: string;
|
|
25
|
+
/** Number of verification attempts made */
|
|
26
|
+
attempts: number;
|
|
27
|
+
/** Maximum allowed attempts */
|
|
28
|
+
maxAttempts: number;
|
|
29
|
+
}
|
|
30
|
+
export interface VerificationResult {
|
|
31
|
+
/** Whether the code was valid and email is now verified */
|
|
32
|
+
success: boolean;
|
|
33
|
+
/** Reason for failure (if not successful) */
|
|
34
|
+
reason?: 'invalid_code' | 'expired' | 'max_attempts' | 'no_pending';
|
|
35
|
+
/** Number of remaining attempts (if applicable) */
|
|
36
|
+
remainingAttempts?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface InitiateVerificationResult {
|
|
39
|
+
/** Whether the verification email was sent successfully */
|
|
40
|
+
sent: boolean;
|
|
41
|
+
/** Error message if sending failed */
|
|
42
|
+
error?: string;
|
|
43
|
+
/** The pending verification state (for passing to verifyCode) */
|
|
44
|
+
pending?: PendingVerification;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generate a cryptographically random 6-digit code.
|
|
48
|
+
*
|
|
49
|
+
* Uses crypto.randomInt (not Math.random) for security.
|
|
50
|
+
*
|
|
51
|
+
* @returns A 6-digit string (zero-padded)
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateVerificationCode(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Hash a verification code with SHA-256.
|
|
56
|
+
*
|
|
57
|
+
* Codes are always stored as hashes, never plaintext.
|
|
58
|
+
*
|
|
59
|
+
* @param code - The plaintext 6-digit code
|
|
60
|
+
* @returns SHA-256 hex digest
|
|
61
|
+
*/
|
|
62
|
+
export declare function hashCode(code: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Initiate email verification by sending a code.
|
|
65
|
+
*
|
|
66
|
+
* @param email - The email address to verify
|
|
67
|
+
* @param deviceFingerprint - Current device fingerprint (for rate limiting and context)
|
|
68
|
+
* @param deviceName - Current device name (for email context)
|
|
69
|
+
* @param baseDir - Optional base directory for config files
|
|
70
|
+
* @returns Result with sent status and pending verification state
|
|
71
|
+
*/
|
|
72
|
+
export declare function initiateVerification(email: string, deviceFingerprint: string, deviceName: string, baseDir?: string): Promise<InitiateVerificationResult>;
|
|
73
|
+
/**
|
|
74
|
+
* Verify a code against a pending verification.
|
|
75
|
+
*
|
|
76
|
+
* @param pending - The pending verification state
|
|
77
|
+
* @param userCode - The code entered by the user
|
|
78
|
+
* @returns Verification result
|
|
79
|
+
*/
|
|
80
|
+
export declare function verifyCode(pending: PendingVerification, userCode: string): VerificationResult;
|
|
81
|
+
/**
|
|
82
|
+
* Complete email verification by saving the config.
|
|
83
|
+
*
|
|
84
|
+
* Call this after verifyCode returns success.
|
|
85
|
+
*
|
|
86
|
+
* @param email - The verified email address
|
|
87
|
+
* @param resendApiKey - Optional custom Resend API key to persist
|
|
88
|
+
* @param baseDir - Optional base directory for config files
|
|
89
|
+
* @returns The saved email config
|
|
90
|
+
*/
|
|
91
|
+
export declare function completeVerification(email: string, resendApiKey?: string, baseDir?: string): EmailConfig;
|
|
92
|
+
/**
|
|
93
|
+
* Check if the code expiry time has passed.
|
|
94
|
+
*
|
|
95
|
+
* @param pending - The pending verification
|
|
96
|
+
* @returns true if the code has expired
|
|
97
|
+
*/
|
|
98
|
+
export declare function isCodeExpired(pending: PendingVerification): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Check if max attempts have been reached.
|
|
101
|
+
*
|
|
102
|
+
* @param pending - The pending verification
|
|
103
|
+
* @returns true if no more attempts are allowed
|
|
104
|
+
*/
|
|
105
|
+
export declare function isMaxAttemptsReached(pending: PendingVerification): boolean;
|
|
106
|
+
//# sourceMappingURL=email.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/signatures/email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAoB3B,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,GAAG,cAAc,GAAG,YAAY,CAAC;IACpE,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,2DAA2D;IAC3D,IAAI,EAAE,OAAO,CAAC;IACd,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAID;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAGjD;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAID;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,0BAA0B,CAAC,CAwDrC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,MAAM,GACf,kBAAkB,CA2BpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,MAAM,GACf,WAAW,CAqBb;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAE1E"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email verification flow for unotoken device signatures.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the email verification process:
|
|
5
|
+
* 1. User provides their email address
|
|
6
|
+
* 2. A 6-digit code is generated and sent via Resend
|
|
7
|
+
* 3. User enters the code to verify ownership
|
|
8
|
+
* 4. On success, email is saved to email-config.json as verified
|
|
9
|
+
*
|
|
10
|
+
* This module connects email-config (persistence) and resend (sending)
|
|
11
|
+
* into a cohesive verification flow.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { randomInt, createHash } from 'node:crypto';
|
|
16
|
+
import { loadEmailConfig, saveEmailConfig, isValidEmail, } from './email-config.js';
|
|
17
|
+
import { sendApprovalEmail, isRateLimited, } from './resend.js';
|
|
18
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
19
|
+
/** Code length (6 digits) */
|
|
20
|
+
const CODE_LENGTH = 6;
|
|
21
|
+
/** Code expiry in milliseconds (5 minutes) */
|
|
22
|
+
const CODE_EXPIRY_MS = 5 * 60 * 1000;
|
|
23
|
+
/** Maximum verification attempts per code */
|
|
24
|
+
const MAX_ATTEMPTS = 3;
|
|
25
|
+
// ─── Code Generation ────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Generate a cryptographically random 6-digit code.
|
|
28
|
+
*
|
|
29
|
+
* Uses crypto.randomInt (not Math.random) for security.
|
|
30
|
+
*
|
|
31
|
+
* @returns A 6-digit string (zero-padded)
|
|
32
|
+
*/
|
|
33
|
+
export function generateVerificationCode() {
|
|
34
|
+
const code = randomInt(0, 10 ** CODE_LENGTH);
|
|
35
|
+
return code.toString().padStart(CODE_LENGTH, '0');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Hash a verification code with SHA-256.
|
|
39
|
+
*
|
|
40
|
+
* Codes are always stored as hashes, never plaintext.
|
|
41
|
+
*
|
|
42
|
+
* @param code - The plaintext 6-digit code
|
|
43
|
+
* @returns SHA-256 hex digest
|
|
44
|
+
*/
|
|
45
|
+
export function hashCode(code) {
|
|
46
|
+
return createHash('sha256').update(code).digest('hex');
|
|
47
|
+
}
|
|
48
|
+
// ─── Verification Flow ──────────────────────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Initiate email verification by sending a code.
|
|
51
|
+
*
|
|
52
|
+
* @param email - The email address to verify
|
|
53
|
+
* @param deviceFingerprint - Current device fingerprint (for rate limiting and context)
|
|
54
|
+
* @param deviceName - Current device name (for email context)
|
|
55
|
+
* @param baseDir - Optional base directory for config files
|
|
56
|
+
* @returns Result with sent status and pending verification state
|
|
57
|
+
*/
|
|
58
|
+
export async function initiateVerification(email, deviceFingerprint, deviceName, baseDir) {
|
|
59
|
+
// Validate email format
|
|
60
|
+
if (!isValidEmail(email)) {
|
|
61
|
+
return {
|
|
62
|
+
sent: false,
|
|
63
|
+
error: `Invalid email address: ${email}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Check rate limit
|
|
67
|
+
if (isRateLimited(deviceFingerprint)) {
|
|
68
|
+
return {
|
|
69
|
+
sent: false,
|
|
70
|
+
error: 'Rate limit exceeded. Maximum 3 verification emails per hour. Please wait before trying again.',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Generate code
|
|
74
|
+
const code = generateVerificationCode();
|
|
75
|
+
const codeHash = hashCode(code);
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const expiresAt = new Date(now.getTime() + CODE_EXPIRY_MS);
|
|
78
|
+
// Build context for the email
|
|
79
|
+
const context = {
|
|
80
|
+
deviceName,
|
|
81
|
+
timestamp: now.toISOString(),
|
|
82
|
+
};
|
|
83
|
+
// Send the email
|
|
84
|
+
const result = await sendApprovalEmail(email, code, context, deviceFingerprint, baseDir);
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
return {
|
|
87
|
+
sent: false,
|
|
88
|
+
error: result.error ?? 'Failed to send verification email',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Create pending verification state
|
|
92
|
+
const pending = {
|
|
93
|
+
email,
|
|
94
|
+
codeHash,
|
|
95
|
+
createdAt: now.toISOString(),
|
|
96
|
+
expiresAt: expiresAt.toISOString(),
|
|
97
|
+
attempts: 0,
|
|
98
|
+
maxAttempts: MAX_ATTEMPTS,
|
|
99
|
+
};
|
|
100
|
+
return { sent: true, pending };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Verify a code against a pending verification.
|
|
104
|
+
*
|
|
105
|
+
* @param pending - The pending verification state
|
|
106
|
+
* @param userCode - The code entered by the user
|
|
107
|
+
* @returns Verification result
|
|
108
|
+
*/
|
|
109
|
+
export function verifyCode(pending, userCode) {
|
|
110
|
+
// Check expiry
|
|
111
|
+
const now = new Date();
|
|
112
|
+
const expiresAt = new Date(pending.expiresAt);
|
|
113
|
+
if (now > expiresAt) {
|
|
114
|
+
return { success: false, reason: 'expired' };
|
|
115
|
+
}
|
|
116
|
+
// Check max attempts
|
|
117
|
+
if (pending.attempts >= pending.maxAttempts) {
|
|
118
|
+
return { success: false, reason: 'max_attempts' };
|
|
119
|
+
}
|
|
120
|
+
// Increment attempts
|
|
121
|
+
pending.attempts += 1;
|
|
122
|
+
// Verify code hash
|
|
123
|
+
const userHash = hashCode(userCode.trim());
|
|
124
|
+
if (userHash !== pending.codeHash) {
|
|
125
|
+
const remaining = pending.maxAttempts - pending.attempts;
|
|
126
|
+
if (remaining <= 0) {
|
|
127
|
+
return { success: false, reason: 'max_attempts', remainingAttempts: 0 };
|
|
128
|
+
}
|
|
129
|
+
return { success: false, reason: 'invalid_code', remainingAttempts: remaining };
|
|
130
|
+
}
|
|
131
|
+
return { success: true };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Complete email verification by saving the config.
|
|
135
|
+
*
|
|
136
|
+
* Call this after verifyCode returns success.
|
|
137
|
+
*
|
|
138
|
+
* @param email - The verified email address
|
|
139
|
+
* @param resendApiKey - Optional custom Resend API key to persist
|
|
140
|
+
* @param baseDir - Optional base directory for config files
|
|
141
|
+
* @returns The saved email config
|
|
142
|
+
*/
|
|
143
|
+
export function completeVerification(email, resendApiKey, baseDir) {
|
|
144
|
+
const config = {
|
|
145
|
+
email,
|
|
146
|
+
verified: true,
|
|
147
|
+
verifiedAt: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
if (resendApiKey) {
|
|
150
|
+
config.resendApiKey = resendApiKey;
|
|
151
|
+
}
|
|
152
|
+
// Preserve existing custom Resend key if not explicitly provided
|
|
153
|
+
if (!resendApiKey) {
|
|
154
|
+
const existing = loadEmailConfig(baseDir);
|
|
155
|
+
if (existing?.resendApiKey) {
|
|
156
|
+
config.resendApiKey = existing.resendApiKey;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
saveEmailConfig(config, baseDir);
|
|
160
|
+
return config;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if the code expiry time has passed.
|
|
164
|
+
*
|
|
165
|
+
* @param pending - The pending verification
|
|
166
|
+
* @returns true if the code has expired
|
|
167
|
+
*/
|
|
168
|
+
export function isCodeExpired(pending) {
|
|
169
|
+
return new Date() > new Date(pending.expiresAt);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Check if max attempts have been reached.
|
|
173
|
+
*
|
|
174
|
+
* @param pending - The pending verification
|
|
175
|
+
* @returns true if no more attempts are allowed
|
|
176
|
+
*/
|
|
177
|
+
export function isMaxAttemptsReached(pending) {
|
|
178
|
+
return pending.attempts >= pending.maxAttempts;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/signatures/email.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device fingerprinting for unotoken device signatures.
|
|
3
|
+
*
|
|
4
|
+
* Generates a stable, unique fingerprint for the current machine by combining:
|
|
5
|
+
* 1. Machine identity (hostname + OS platform + username)
|
|
6
|
+
* 2. A per-install random salt stored at ~/.yokotoken/device-install-id
|
|
7
|
+
*
|
|
8
|
+
* The per-install salt ensures that even identical VMs or containers get
|
|
9
|
+
* different fingerprints. The machine identity ensures that the same install
|
|
10
|
+
* on the same machine always produces the same fingerprint (deterministic).
|
|
11
|
+
*
|
|
12
|
+
* For cloud/container environments, each new container instance will generate
|
|
13
|
+
* a new device-install-id and thus a new fingerprint -- this is intentional,
|
|
14
|
+
* as each container is treated as a separate device.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Get the path to the device-install-id file.
|
|
20
|
+
*
|
|
21
|
+
* @param baseDir - Optional base directory (default: ~/.yokotoken)
|
|
22
|
+
* @returns Absolute path to device-install-id
|
|
23
|
+
*/
|
|
24
|
+
export declare function getInstallIdPath(baseDir?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Read or create the per-install random salt.
|
|
27
|
+
*
|
|
28
|
+
* On first call, generates a 32-byte random value and persists it.
|
|
29
|
+
* On subsequent calls, reads the persisted value.
|
|
30
|
+
*
|
|
31
|
+
* @param baseDir - Optional base directory (default: ~/.yokotoken)
|
|
32
|
+
* @returns The install ID as a hex string
|
|
33
|
+
*/
|
|
34
|
+
export declare function ensureInstallId(baseDir?: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Collect machine identity components.
|
|
37
|
+
*
|
|
38
|
+
* Returns a deterministic string derived from the machine's hostname,
|
|
39
|
+
* OS platform, and current username. This provides a "best effort"
|
|
40
|
+
* machine identity that is stable across restarts.
|
|
41
|
+
*
|
|
42
|
+
* @returns A colon-separated identity string
|
|
43
|
+
*/
|
|
44
|
+
export declare function getMachineIdentity(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Generate a stable device fingerprint.
|
|
47
|
+
*
|
|
48
|
+
* The fingerprint is a SHA-256 hash of:
|
|
49
|
+
* machineIdentity + ":" + installId
|
|
50
|
+
*
|
|
51
|
+
* Properties:
|
|
52
|
+
* - Deterministic: same machine + same install-id = same fingerprint
|
|
53
|
+
* - Unique across machines: different hostname/OS/username = different hash
|
|
54
|
+
* - Unique across installs: different install-id = different hash (even on same machine)
|
|
55
|
+
* - Not reversible: SHA-256 hash cannot reveal machine details
|
|
56
|
+
*
|
|
57
|
+
* @param baseDir - Optional base directory for the install-id file (default: ~/.yokotoken)
|
|
58
|
+
* @returns A 64-character hex string (SHA-256 hash)
|
|
59
|
+
*/
|
|
60
|
+
export declare function generateDeviceFingerprint(baseDir?: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Generate a user-friendly device name from system info.
|
|
63
|
+
*
|
|
64
|
+
* Format: "{username}@{hostname}" — human-readable but not guaranteed unique.
|
|
65
|
+
* Used as the default display name for a device in the known_devices registry.
|
|
66
|
+
*
|
|
67
|
+
* @returns A human-friendly device name string
|
|
68
|
+
*/
|
|
69
|
+
export declare function getDefaultDeviceName(): string;
|
|
70
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/signatures/fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAeH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAID;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAW7C"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device fingerprinting for unotoken device signatures.
|
|
3
|
+
*
|
|
4
|
+
* Generates a stable, unique fingerprint for the current machine by combining:
|
|
5
|
+
* 1. Machine identity (hostname + OS platform + username)
|
|
6
|
+
* 2. A per-install random salt stored at ~/.yokotoken/device-install-id
|
|
7
|
+
*
|
|
8
|
+
* The per-install salt ensures that even identical VMs or containers get
|
|
9
|
+
* different fingerprints. The machine identity ensures that the same install
|
|
10
|
+
* on the same machine always produces the same fingerprint (deterministic).
|
|
11
|
+
*
|
|
12
|
+
* For cloud/container environments, each new container instance will generate
|
|
13
|
+
* a new device-install-id and thus a new fingerprint -- this is intentional,
|
|
14
|
+
* as each container is treated as a separate device.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
19
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
20
|
+
import { hostname, platform, userInfo } from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { getDeviceDir } from '../oauth/device-secret.js';
|
|
23
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
24
|
+
const INSTALL_ID_FILENAME = 'device-install-id';
|
|
25
|
+
const INSTALL_ID_BYTES = 32;
|
|
26
|
+
// ─── Install ID (per-install salt) ──────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Get the path to the device-install-id file.
|
|
29
|
+
*
|
|
30
|
+
* @param baseDir - Optional base directory (default: ~/.yokotoken)
|
|
31
|
+
* @returns Absolute path to device-install-id
|
|
32
|
+
*/
|
|
33
|
+
export function getInstallIdPath(baseDir) {
|
|
34
|
+
return path.join(baseDir ?? getDeviceDir(), INSTALL_ID_FILENAME);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read or create the per-install random salt.
|
|
38
|
+
*
|
|
39
|
+
* On first call, generates a 32-byte random value and persists it.
|
|
40
|
+
* On subsequent calls, reads the persisted value.
|
|
41
|
+
*
|
|
42
|
+
* @param baseDir - Optional base directory (default: ~/.yokotoken)
|
|
43
|
+
* @returns The install ID as a hex string
|
|
44
|
+
*/
|
|
45
|
+
export function ensureInstallId(baseDir) {
|
|
46
|
+
const dir = baseDir ?? getDeviceDir();
|
|
47
|
+
const idPath = getInstallIdPath(dir);
|
|
48
|
+
if (existsSync(idPath)) {
|
|
49
|
+
return readFileSync(idPath, 'utf-8').trim();
|
|
50
|
+
}
|
|
51
|
+
// Create directory if needed
|
|
52
|
+
if (!existsSync(dir)) {
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
const installId = randomBytes(INSTALL_ID_BYTES).toString('hex');
|
|
56
|
+
writeFileSync(idPath, installId + '\n', 'utf-8');
|
|
57
|
+
return installId;
|
|
58
|
+
}
|
|
59
|
+
// ─── Machine Identity ──────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Collect machine identity components.
|
|
62
|
+
*
|
|
63
|
+
* Returns a deterministic string derived from the machine's hostname,
|
|
64
|
+
* OS platform, and current username. This provides a "best effort"
|
|
65
|
+
* machine identity that is stable across restarts.
|
|
66
|
+
*
|
|
67
|
+
* @returns A colon-separated identity string
|
|
68
|
+
*/
|
|
69
|
+
export function getMachineIdentity() {
|
|
70
|
+
const host = hostname();
|
|
71
|
+
const os = platform();
|
|
72
|
+
let username;
|
|
73
|
+
try {
|
|
74
|
+
username = userInfo().username;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// userInfo() can throw on some systems (e.g., containers without /etc/passwd)
|
|
78
|
+
username = process.env.USER || process.env.USERNAME || 'unknown';
|
|
79
|
+
}
|
|
80
|
+
return `${host}:${os}:${username}`;
|
|
81
|
+
}
|
|
82
|
+
// ─── Fingerprint Generation ─────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Generate a stable device fingerprint.
|
|
85
|
+
*
|
|
86
|
+
* The fingerprint is a SHA-256 hash of:
|
|
87
|
+
* machineIdentity + ":" + installId
|
|
88
|
+
*
|
|
89
|
+
* Properties:
|
|
90
|
+
* - Deterministic: same machine + same install-id = same fingerprint
|
|
91
|
+
* - Unique across machines: different hostname/OS/username = different hash
|
|
92
|
+
* - Unique across installs: different install-id = different hash (even on same machine)
|
|
93
|
+
* - Not reversible: SHA-256 hash cannot reveal machine details
|
|
94
|
+
*
|
|
95
|
+
* @param baseDir - Optional base directory for the install-id file (default: ~/.yokotoken)
|
|
96
|
+
* @returns A 64-character hex string (SHA-256 hash)
|
|
97
|
+
*/
|
|
98
|
+
export function generateDeviceFingerprint(baseDir) {
|
|
99
|
+
const machineId = getMachineIdentity();
|
|
100
|
+
const installId = ensureInstallId(baseDir);
|
|
101
|
+
const input = `${machineId}:${installId}`;
|
|
102
|
+
return createHash('sha256').update(input).digest('hex');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Generate a user-friendly device name from system info.
|
|
106
|
+
*
|
|
107
|
+
* Format: "{username}@{hostname}" — human-readable but not guaranteed unique.
|
|
108
|
+
* Used as the default display name for a device in the known_devices registry.
|
|
109
|
+
*
|
|
110
|
+
* @returns A human-friendly device name string
|
|
111
|
+
*/
|
|
112
|
+
export function getDefaultDeviceName() {
|
|
113
|
+
const host = hostname();
|
|
114
|
+
let username;
|
|
115
|
+
try {
|
|
116
|
+
username = userInfo().username;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
username = process.env.USER || process.env.USERNAME || 'unknown';
|
|
120
|
+
}
|
|
121
|
+
return `${username}@${host}`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/signatures/fingerprint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device approval guard for unotoken.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a guard that checks whether the current device is
|
|
5
|
+
* approved before allowing vault operations. It integrates:
|
|
6
|
+
* - Device fingerprinting (fingerprint.ts)
|
|
7
|
+
* - Known devices registry (devices.ts)
|
|
8
|
+
* - Email configuration (email-config.ts)
|
|
9
|
+
* - Approval codes (approval-codes.ts)
|
|
10
|
+
* - Email sending (resend.ts)
|
|
11
|
+
*
|
|
12
|
+
* The guard runs BEFORE vault unlock -- a new device cannot even attempt
|
|
13
|
+
* to unlock without approval.
|
|
14
|
+
*
|
|
15
|
+
* Usage patterns:
|
|
16
|
+
* 1. CLI (TTY): Prompts interactively for the 6-digit code
|
|
17
|
+
* 2. SDK (programmatic): Throws DeviceApprovalRequired with instructions
|
|
18
|
+
*
|
|
19
|
+
* Graceful degradation: If no email is configured, the guard allows the
|
|
20
|
+
* operation to proceed (signatures are opt-in via email config).
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
export interface DeviceGuardOptions {
|
|
25
|
+
/** Base directory for config/database files (default: ~/.yokotoken) */
|
|
26
|
+
baseDir?: string;
|
|
27
|
+
/** Whether running in a TTY context (can prompt user). Default: auto-detect from process.stdin */
|
|
28
|
+
isTTY?: boolean;
|
|
29
|
+
/** Function to read a line from the user (for TTY prompting). Injected for testability. */
|
|
30
|
+
readLine?: (prompt: string) => Promise<string>;
|
|
31
|
+
/** Function to write output (for TTY messaging). Injected for testability. */
|
|
32
|
+
writeOutput?: (message: string) => void;
|
|
33
|
+
/** Function to write error output. Injected for testability. */
|
|
34
|
+
writeError?: (message: string) => void;
|
|
35
|
+
}
|
|
36
|
+
export interface DeviceGuardResult {
|
|
37
|
+
/** Whether the operation should proceed */
|
|
38
|
+
allowed: boolean;
|
|
39
|
+
/** Whether the device was newly approved during this check */
|
|
40
|
+
newlyApproved: boolean;
|
|
41
|
+
/** Whether the device was auto-approved (first device, no email needed) */
|
|
42
|
+
autoApproved: boolean;
|
|
43
|
+
/** Device name (for display) */
|
|
44
|
+
deviceName: string;
|
|
45
|
+
/** Device fingerprint */
|
|
46
|
+
fingerprint: string;
|
|
47
|
+
/** Reason the operation was blocked (if allowed = false) */
|
|
48
|
+
reason?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Error thrown when a device requires approval but is used programmatically
|
|
52
|
+
* (no TTY to prompt the user).
|
|
53
|
+
*
|
|
54
|
+
* The caller (app/cloud service) is responsible for:
|
|
55
|
+
* 1. Sending the code via approveDevice()
|
|
56
|
+
* 2. Collecting the code from the user
|
|
57
|
+
* 3. Calling completeApproval() with the code
|
|
58
|
+
*/
|
|
59
|
+
export declare class DeviceApprovalRequired extends Error {
|
|
60
|
+
/** The device fingerprint that needs approval */
|
|
61
|
+
fingerprint: string;
|
|
62
|
+
/** The masked email where the code was sent (or null if not sent) */
|
|
63
|
+
maskedEmail: string | null;
|
|
64
|
+
/** Whether a code was actually sent */
|
|
65
|
+
codeSent: boolean;
|
|
66
|
+
constructor(fingerprint: string, maskedEmail: string | null, codeSent: boolean);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if the current device is approved and handle the approval flow.
|
|
70
|
+
*
|
|
71
|
+
* This is the main entry point for device checks. It should be called
|
|
72
|
+
* before any vault operation (unlock, get, set, list).
|
|
73
|
+
*
|
|
74
|
+
* Flow:
|
|
75
|
+
* 1. Generate device fingerprint
|
|
76
|
+
* 2. Check if device is known (auto-approves first device)
|
|
77
|
+
* 3. If known: allow operation, update last_seen
|
|
78
|
+
* 4. If unknown:
|
|
79
|
+
* a. If no email configured: allow (graceful degradation)
|
|
80
|
+
* b. If TTY: send code, prompt user, verify, register device
|
|
81
|
+
* c. If no TTY: throw DeviceApprovalRequired
|
|
82
|
+
*
|
|
83
|
+
* @param options - Guard configuration
|
|
84
|
+
* @returns Result indicating whether the operation should proceed
|
|
85
|
+
* @throws DeviceApprovalRequired when used programmatically and device is unknown
|
|
86
|
+
*/
|
|
87
|
+
export declare function deviceGuard(options?: DeviceGuardOptions): Promise<DeviceGuardResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Programmatically approve a device with an approval code.
|
|
90
|
+
*
|
|
91
|
+
* For use by SDK consumers who caught DeviceApprovalRequired and collected
|
|
92
|
+
* the code from the user via their own UI.
|
|
93
|
+
*
|
|
94
|
+
* @param fingerprint - The device fingerprint from the error
|
|
95
|
+
* @param code - The 6-digit code entered by the user
|
|
96
|
+
* @param baseDir - Optional base directory for config/database files
|
|
97
|
+
* @returns Object with `approved` boolean and optional `reason`
|
|
98
|
+
*/
|
|
99
|
+
export declare function approveDevice(fingerprint: string, code: string, baseDir?: string): Promise<{
|
|
100
|
+
approved: boolean;
|
|
101
|
+
deviceName: string;
|
|
102
|
+
reason?: string;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Send an approval code to the user's configured email for a specific device.
|
|
106
|
+
*
|
|
107
|
+
* For use by SDK consumers who need to initiate the approval flow programmatically.
|
|
108
|
+
*
|
|
109
|
+
* @param fingerprint - The device fingerprint
|
|
110
|
+
* @param baseDir - Optional base directory for config/database files
|
|
111
|
+
* @returns Object with `sent` boolean, `maskedEmail`, and optional `error`
|
|
112
|
+
*/
|
|
113
|
+
export declare function sendDeviceApprovalCode(fingerprint: string, baseDir?: string): Promise<{
|
|
114
|
+
sent: boolean;
|
|
115
|
+
maskedEmail?: string;
|
|
116
|
+
error?: string;
|
|
117
|
+
}>;
|
|
118
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/signatures/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAUH,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,gEAAgE;IAChE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAC;IACvB,2EAA2E;IAC3E,YAAY,EAAE,OAAO,CAAC;IACtB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;gBAGhB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,QAAQ,EAAE,OAAO;CAcpB;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAwF5B;AAgJD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsBrE;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA6ClE"}
|