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,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval code engine for unotoken device signatures.
|
|
3
|
+
*
|
|
4
|
+
* Generates and verifies 6-digit numeric codes used to approve new devices.
|
|
5
|
+
* Codes are cryptographically random, time-limited, and rate-limited.
|
|
6
|
+
*
|
|
7
|
+
* Security properties:
|
|
8
|
+
* - Codes generated with crypto.randomInt (not Math.random)
|
|
9
|
+
* - Codes stored as SHA-256 hashes in the database (never plaintext)
|
|
10
|
+
* - Codes expire after 5 minutes (configurable)
|
|
11
|
+
* - Maximum 3 verification attempts per code (brute-force protection)
|
|
12
|
+
* - Maximum 3 codes per device fingerprint per hour (rate limiting)
|
|
13
|
+
* - Expired/used codes cleaned up on next verification attempt
|
|
14
|
+
*
|
|
15
|
+
* With a 6-digit code (1M possibilities), 3-attempt limit, and 5-minute
|
|
16
|
+
* expiry, brute-force is computationally infeasible.
|
|
17
|
+
*
|
|
18
|
+
* Table: approval_codes
|
|
19
|
+
* - id: UUID primary key
|
|
20
|
+
* - device_fingerprint: SHA-256 hex string identifying the requesting device
|
|
21
|
+
* - code: SHA-256 hash of the 6-digit numeric code
|
|
22
|
+
* - created_at: ISO 8601 timestamp
|
|
23
|
+
* - expires_at: ISO 8601 timestamp (created_at + CODE_EXPIRY_MS)
|
|
24
|
+
* - used: 0 or 1 (SQLite boolean)
|
|
25
|
+
* - attempts: number of verification attempts made
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import initSqlJs from 'sql.js';
|
|
30
|
+
import { randomUUID, randomInt, createHash } from 'node:crypto';
|
|
31
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
import { getDeviceDir } from '../oauth/device-secret.js';
|
|
34
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
35
|
+
/** Default code expiry: 5 minutes */
|
|
36
|
+
const DEFAULT_CODE_EXPIRY_MS = 5 * 60 * 1000;
|
|
37
|
+
/** Maximum verification attempts per code */
|
|
38
|
+
const MAX_ATTEMPTS = 3;
|
|
39
|
+
/** Maximum codes per device fingerprint per hour */
|
|
40
|
+
const MAX_CODES_PER_HOUR = 3;
|
|
41
|
+
/** Rate limit window: 1 hour */
|
|
42
|
+
const RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000;
|
|
43
|
+
const CODES_DB_FILENAME = 'devices.db';
|
|
44
|
+
// ─── Hashing ────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Hash a 6-digit code with SHA-256 for secure storage.
|
|
47
|
+
*
|
|
48
|
+
* Even if the database is compromised, the attacker cannot recover
|
|
49
|
+
* valid codes from their hashes.
|
|
50
|
+
*
|
|
51
|
+
* @param code - The plaintext 6-digit code
|
|
52
|
+
* @returns SHA-256 hex digest
|
|
53
|
+
*/
|
|
54
|
+
export function hashCode(code) {
|
|
55
|
+
return createHash('sha256').update(code).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
// ─── SQL.js Initialization ──────────────────────────────────────────
|
|
58
|
+
/** Cached sql.js module (loaded once). */
|
|
59
|
+
let sqlJsModule = null;
|
|
60
|
+
async function getSqlJs() {
|
|
61
|
+
if (!sqlJsModule) {
|
|
62
|
+
sqlJsModule = await initSqlJs();
|
|
63
|
+
}
|
|
64
|
+
return sqlJsModule;
|
|
65
|
+
}
|
|
66
|
+
// ─── ApprovalCodesDatabase ──────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Manages the approval_codes SQLite table.
|
|
69
|
+
*
|
|
70
|
+
* Shares the same database file as DevicesDatabase (devices.db) so that
|
|
71
|
+
* both tables coexist in the same SQLite file. This simplifies file
|
|
72
|
+
* management while keeping concerns separated.
|
|
73
|
+
*/
|
|
74
|
+
export class ApprovalCodesDatabase {
|
|
75
|
+
db;
|
|
76
|
+
dbPath;
|
|
77
|
+
codeExpiryMs;
|
|
78
|
+
constructor(db, dbPath, codeExpiryMs) {
|
|
79
|
+
this.db = db;
|
|
80
|
+
this.dbPath = dbPath;
|
|
81
|
+
this.codeExpiryMs = codeExpiryMs;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Open or create the approval codes database.
|
|
85
|
+
*
|
|
86
|
+
* Uses the same devices.db file as DevicesDatabase. Creates the
|
|
87
|
+
* approval_codes table if it does not exist.
|
|
88
|
+
*
|
|
89
|
+
* @param baseDir - Optional base directory (default: ~/.yokotoken)
|
|
90
|
+
* @param codeExpiryMs - Code expiry in milliseconds (default: 5 minutes)
|
|
91
|
+
* @returns A ready-to-use ApprovalCodesDatabase instance
|
|
92
|
+
*/
|
|
93
|
+
static async open(baseDir, codeExpiryMs) {
|
|
94
|
+
const dir = baseDir ?? getDeviceDir();
|
|
95
|
+
const dbPath = path.join(dir, CODES_DB_FILENAME);
|
|
96
|
+
if (!existsSync(dir)) {
|
|
97
|
+
mkdirSync(dir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
const SQL = await getSqlJs();
|
|
100
|
+
let db;
|
|
101
|
+
if (existsSync(dbPath)) {
|
|
102
|
+
const buffer = readFileSync(dbPath);
|
|
103
|
+
db = new SQL.Database(buffer);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
db = new SQL.Database();
|
|
107
|
+
}
|
|
108
|
+
const instance = new ApprovalCodesDatabase(db, dbPath, codeExpiryMs ?? DEFAULT_CODE_EXPIRY_MS);
|
|
109
|
+
instance.initSchema();
|
|
110
|
+
return instance;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Initialize the approval_codes table if it does not exist.
|
|
114
|
+
*/
|
|
115
|
+
initSchema() {
|
|
116
|
+
this.db.run(`
|
|
117
|
+
CREATE TABLE IF NOT EXISTS approval_codes (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
device_fingerprint TEXT NOT NULL,
|
|
120
|
+
code TEXT NOT NULL,
|
|
121
|
+
created_at TEXT NOT NULL,
|
|
122
|
+
expires_at TEXT NOT NULL,
|
|
123
|
+
used INTEGER NOT NULL DEFAULT 0,
|
|
124
|
+
attempts INTEGER NOT NULL DEFAULT 0
|
|
125
|
+
)
|
|
126
|
+
`);
|
|
127
|
+
this.save();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Persist the in-memory database to disk.
|
|
131
|
+
*/
|
|
132
|
+
save() {
|
|
133
|
+
const dir = path.dirname(this.dbPath);
|
|
134
|
+
if (!existsSync(dir)) {
|
|
135
|
+
mkdirSync(dir, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
const data = this.db.export();
|
|
138
|
+
writeFileSync(this.dbPath, Buffer.from(data));
|
|
139
|
+
}
|
|
140
|
+
// ─── Code Generation ──────────────────────────────────────────────
|
|
141
|
+
/**
|
|
142
|
+
* Generate a cryptographically random 6-digit numeric code.
|
|
143
|
+
*
|
|
144
|
+
* Uses crypto.randomInt for uniform distribution across [0, 999999].
|
|
145
|
+
* The result is zero-padded to always return exactly 6 digits.
|
|
146
|
+
*
|
|
147
|
+
* @returns A 6-digit string (e.g., "042817")
|
|
148
|
+
*/
|
|
149
|
+
static generateCode() {
|
|
150
|
+
const num = randomInt(0, 1_000_000);
|
|
151
|
+
return num.toString().padStart(6, '0');
|
|
152
|
+
}
|
|
153
|
+
// ─── Rate Limiting ────────────────────────────────────────────────
|
|
154
|
+
/**
|
|
155
|
+
* Check whether a new code can be created for the given device fingerprint.
|
|
156
|
+
*
|
|
157
|
+
* Enforces: max 3 codes per device per hour.
|
|
158
|
+
*
|
|
159
|
+
* @param deviceFingerprint - The device fingerprint
|
|
160
|
+
* @returns true if creating a new code is allowed
|
|
161
|
+
*/
|
|
162
|
+
canCreateCode(deviceFingerprint) {
|
|
163
|
+
const windowStart = new Date(Date.now() - RATE_LIMIT_WINDOW_MS).toISOString();
|
|
164
|
+
const stmt = this.db.prepare('SELECT COUNT(*) as cnt FROM approval_codes WHERE device_fingerprint = ? AND created_at > ?');
|
|
165
|
+
stmt.bind([deviceFingerprint, windowStart]);
|
|
166
|
+
let count = 0;
|
|
167
|
+
if (stmt.step()) {
|
|
168
|
+
const row = stmt.getAsObject();
|
|
169
|
+
count = row.cnt;
|
|
170
|
+
}
|
|
171
|
+
stmt.free();
|
|
172
|
+
return count < MAX_CODES_PER_HOUR;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the number of codes created for a device in the current hour window.
|
|
176
|
+
*
|
|
177
|
+
* @param deviceFingerprint - The device fingerprint
|
|
178
|
+
* @returns Number of codes created in the last hour
|
|
179
|
+
*/
|
|
180
|
+
codesCreatedInWindow(deviceFingerprint) {
|
|
181
|
+
const windowStart = new Date(Date.now() - RATE_LIMIT_WINDOW_MS).toISOString();
|
|
182
|
+
const stmt = this.db.prepare('SELECT COUNT(*) as cnt FROM approval_codes WHERE device_fingerprint = ? AND created_at > ?');
|
|
183
|
+
stmt.bind([deviceFingerprint, windowStart]);
|
|
184
|
+
let count = 0;
|
|
185
|
+
if (stmt.step()) {
|
|
186
|
+
const row = stmt.getAsObject();
|
|
187
|
+
count = row.cnt;
|
|
188
|
+
}
|
|
189
|
+
stmt.free();
|
|
190
|
+
return count;
|
|
191
|
+
}
|
|
192
|
+
// ─── Code Creation ────────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* Create a new approval code for a device.
|
|
195
|
+
*
|
|
196
|
+
* @param deviceFingerprint - The requesting device's fingerprint
|
|
197
|
+
* @returns Object with the plaintext code (for sending via email) and the record ID,
|
|
198
|
+
* or null if rate-limited
|
|
199
|
+
*/
|
|
200
|
+
createCode(deviceFingerprint) {
|
|
201
|
+
// Rate limit check
|
|
202
|
+
if (!this.canCreateCode(deviceFingerprint)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const code = ApprovalCodesDatabase.generateCode();
|
|
206
|
+
const codeHash = hashCode(code);
|
|
207
|
+
const id = randomUUID();
|
|
208
|
+
const now = new Date();
|
|
209
|
+
const createdAt = now.toISOString();
|
|
210
|
+
const expiresAt = new Date(now.getTime() + this.codeExpiryMs).toISOString();
|
|
211
|
+
this.db.run(`INSERT INTO approval_codes (id, device_fingerprint, code, created_at, expires_at, used, attempts)
|
|
212
|
+
VALUES (?, ?, ?, ?, ?, 0, 0)`, [id, deviceFingerprint, codeHash, createdAt, expiresAt]);
|
|
213
|
+
this.save();
|
|
214
|
+
return { code, id };
|
|
215
|
+
}
|
|
216
|
+
// ─── Verification ─────────────────────────────────────────────────
|
|
217
|
+
/**
|
|
218
|
+
* Verify an approval code entered by the user.
|
|
219
|
+
*
|
|
220
|
+
* Checks:
|
|
221
|
+
* 1. Find the most recent active code for this device fingerprint
|
|
222
|
+
* 2. Check if the code has been used
|
|
223
|
+
* 3. Check if the code has expired
|
|
224
|
+
* 4. Check if max attempts exceeded
|
|
225
|
+
* 5. Compare the hash of user input against stored hash
|
|
226
|
+
* 6. If mismatch, increment attempts
|
|
227
|
+
*
|
|
228
|
+
* Also cleans up expired/used codes on each verification attempt.
|
|
229
|
+
*
|
|
230
|
+
* @param deviceFingerprint - The device fingerprint
|
|
231
|
+
* @param userInput - The 6-digit code entered by the user
|
|
232
|
+
* @returns VerifyResult with valid boolean and optional reason
|
|
233
|
+
*/
|
|
234
|
+
verifyCode(deviceFingerprint, userInput) {
|
|
235
|
+
// Clean up expired/used codes first
|
|
236
|
+
this.cleanup();
|
|
237
|
+
// Find the most recent active (unused, non-expired, under attempt limit) code
|
|
238
|
+
const now = new Date().toISOString();
|
|
239
|
+
const stmt = this.db.prepare(`SELECT id, code, expires_at, used, attempts
|
|
240
|
+
FROM approval_codes
|
|
241
|
+
WHERE device_fingerprint = ?
|
|
242
|
+
AND used = 0
|
|
243
|
+
AND attempts < ?
|
|
244
|
+
ORDER BY created_at DESC
|
|
245
|
+
LIMIT 1`);
|
|
246
|
+
stmt.bind([deviceFingerprint, MAX_ATTEMPTS]);
|
|
247
|
+
let record = null;
|
|
248
|
+
if (stmt.step()) {
|
|
249
|
+
const row = stmt.getAsObject();
|
|
250
|
+
record = {
|
|
251
|
+
id: row.id,
|
|
252
|
+
code: row.code,
|
|
253
|
+
expires_at: row.expires_at,
|
|
254
|
+
used: row.used,
|
|
255
|
+
attempts: row.attempts,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
stmt.free();
|
|
259
|
+
// No active code found
|
|
260
|
+
if (!record) {
|
|
261
|
+
return {
|
|
262
|
+
valid: false,
|
|
263
|
+
reason: 'No active approval code found. Request a new code.',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Check expiry
|
|
267
|
+
if (now > record.expires_at) {
|
|
268
|
+
return {
|
|
269
|
+
valid: false,
|
|
270
|
+
reason: 'Code expired. Request a new code.',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Check attempt limit (should not happen due to query filter, but defensive)
|
|
274
|
+
if (record.attempts >= MAX_ATTEMPTS) {
|
|
275
|
+
return {
|
|
276
|
+
valid: false,
|
|
277
|
+
reason: `Maximum verification attempts (${MAX_ATTEMPTS}) exceeded. Request a new code.`,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// Hash the user input and compare
|
|
281
|
+
const inputHash = hashCode(userInput);
|
|
282
|
+
if (inputHash === record.code) {
|
|
283
|
+
// Mark as used
|
|
284
|
+
this.db.run('UPDATE approval_codes SET used = 1 WHERE id = ?', [record.id]);
|
|
285
|
+
this.save();
|
|
286
|
+
return { valid: true };
|
|
287
|
+
}
|
|
288
|
+
// Wrong code -- increment attempts
|
|
289
|
+
const newAttempts = record.attempts + 1;
|
|
290
|
+
this.db.run('UPDATE approval_codes SET attempts = ? WHERE id = ?', [newAttempts, record.id]);
|
|
291
|
+
this.save();
|
|
292
|
+
const remaining = MAX_ATTEMPTS - newAttempts;
|
|
293
|
+
if (remaining <= 0) {
|
|
294
|
+
return {
|
|
295
|
+
valid: false,
|
|
296
|
+
reason: `Invalid code. Maximum attempts (${MAX_ATTEMPTS}) reached. Request a new code.`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
valid: false,
|
|
301
|
+
reason: `Invalid code. ${remaining} attempt${remaining === 1 ? '' : 's'} remaining.`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
// ─── Cleanup ──────────────────────────────────────────────────────
|
|
305
|
+
/**
|
|
306
|
+
* Remove expired and used codes from the database.
|
|
307
|
+
*
|
|
308
|
+
* Called automatically during verification, but can also be called
|
|
309
|
+
* manually for maintenance.
|
|
310
|
+
*/
|
|
311
|
+
cleanup() {
|
|
312
|
+
const now = new Date().toISOString();
|
|
313
|
+
// Remove expired codes
|
|
314
|
+
this.db.run('DELETE FROM approval_codes WHERE expires_at < ?', [now]);
|
|
315
|
+
// Remove used codes
|
|
316
|
+
this.db.run('DELETE FROM approval_codes WHERE used = 1');
|
|
317
|
+
// Remove codes that have hit the max attempt limit
|
|
318
|
+
this.db.run('DELETE FROM approval_codes WHERE attempts >= ?', [MAX_ATTEMPTS]);
|
|
319
|
+
this.save();
|
|
320
|
+
}
|
|
321
|
+
// ─── Queries ──────────────────────────────────────────────────────
|
|
322
|
+
/**
|
|
323
|
+
* Get the number of active (unused, non-expired, under attempt limit) codes
|
|
324
|
+
* for a device fingerprint.
|
|
325
|
+
*
|
|
326
|
+
* @param deviceFingerprint - The device fingerprint
|
|
327
|
+
* @returns Number of active codes
|
|
328
|
+
*/
|
|
329
|
+
activeCodeCount(deviceFingerprint) {
|
|
330
|
+
const now = new Date().toISOString();
|
|
331
|
+
const stmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM approval_codes
|
|
332
|
+
WHERE device_fingerprint = ?
|
|
333
|
+
AND used = 0
|
|
334
|
+
AND expires_at > ?
|
|
335
|
+
AND attempts < ?`);
|
|
336
|
+
stmt.bind([deviceFingerprint, now, MAX_ATTEMPTS]);
|
|
337
|
+
let count = 0;
|
|
338
|
+
if (stmt.step()) {
|
|
339
|
+
const row = stmt.getAsObject();
|
|
340
|
+
count = row.cnt;
|
|
341
|
+
}
|
|
342
|
+
stmt.free();
|
|
343
|
+
return count;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get the total number of codes in the database (for testing/debugging).
|
|
347
|
+
*
|
|
348
|
+
* @returns Total number of approval_codes rows
|
|
349
|
+
*/
|
|
350
|
+
totalCodeCount() {
|
|
351
|
+
const stmt = this.db.prepare('SELECT COUNT(*) as cnt FROM approval_codes');
|
|
352
|
+
let count = 0;
|
|
353
|
+
if (stmt.step()) {
|
|
354
|
+
const row = stmt.getAsObject();
|
|
355
|
+
count = row.cnt;
|
|
356
|
+
}
|
|
357
|
+
stmt.free();
|
|
358
|
+
return count;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Close the database connection.
|
|
362
|
+
*/
|
|
363
|
+
close() {
|
|
364
|
+
this.db.close();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// ─── High-level convenience functions ───────────────────────────────
|
|
368
|
+
/**
|
|
369
|
+
* Generate a new approval code for a device.
|
|
370
|
+
*
|
|
371
|
+
* Convenience wrapper that opens the database, creates a code, and closes.
|
|
372
|
+
*
|
|
373
|
+
* @param deviceFingerprint - The requesting device's fingerprint
|
|
374
|
+
* @param baseDir - Optional base directory for the database
|
|
375
|
+
* @param codeExpiryMs - Optional code expiry in ms (default: 5 minutes)
|
|
376
|
+
* @returns Object with plaintext code and record ID, or null if rate-limited
|
|
377
|
+
*/
|
|
378
|
+
export async function createApprovalCode(deviceFingerprint, baseDir, codeExpiryMs) {
|
|
379
|
+
const db = await ApprovalCodesDatabase.open(baseDir, codeExpiryMs);
|
|
380
|
+
try {
|
|
381
|
+
return db.createCode(deviceFingerprint);
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
db.close();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Verify a user-entered approval code.
|
|
389
|
+
*
|
|
390
|
+
* Convenience wrapper that opens the database, verifies, and closes.
|
|
391
|
+
*
|
|
392
|
+
* @param deviceFingerprint - The device fingerprint
|
|
393
|
+
* @param userInput - The 6-digit code entered by the user
|
|
394
|
+
* @param baseDir - Optional base directory for the database
|
|
395
|
+
* @param codeExpiryMs - Optional code expiry in ms (default: 5 minutes)
|
|
396
|
+
* @returns VerifyResult with valid boolean and optional reason
|
|
397
|
+
*/
|
|
398
|
+
export async function verifyApprovalCode(deviceFingerprint, userInput, baseDir, codeExpiryMs) {
|
|
399
|
+
const db = await ApprovalCodesDatabase.open(baseDir, codeExpiryMs);
|
|
400
|
+
try {
|
|
401
|
+
return db.verifyCode(deviceFingerprint, userInput);
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
db.close();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=approval-codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-codes.js","sourceRoot":"","sources":["../../src/signatures/approval-codes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device management commands for unotoken CLI.
|
|
3
|
+
*
|
|
4
|
+
* Provides the logic behind `unotoken device list|rename|remove`:
|
|
5
|
+
* - List all approved devices with current-device indicator
|
|
6
|
+
* - Rename a device by fingerprint prefix
|
|
7
|
+
* - Remove a device (with confirmation and safety checks)
|
|
8
|
+
* - Remove all devices except current (nuclear option)
|
|
9
|
+
*
|
|
10
|
+
* Fingerprint prefix matching works like git SHA prefixes -- the user
|
|
11
|
+
* provides the first N characters and the command finds the unique match.
|
|
12
|
+
* If ambiguous, the command lists matches and asks the user to be more specific.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { DevicesDatabase, type KnownDevice } from './devices.js';
|
|
17
|
+
export interface DeviceListResult {
|
|
18
|
+
devices: DeviceListEntry[];
|
|
19
|
+
currentFingerprint: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DeviceListEntry extends KnownDevice {
|
|
22
|
+
/** Whether this is the current device */
|
|
23
|
+
is_current: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface DeviceRenameResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
fingerprint: string;
|
|
28
|
+
oldName: string;
|
|
29
|
+
newName: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface DeviceRemoveResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
fingerprint: string;
|
|
35
|
+
name: string;
|
|
36
|
+
wasCurrentDevice: boolean;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface DeviceRemoveAllResult {
|
|
40
|
+
success: boolean;
|
|
41
|
+
removedCount: number;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface PrefixMatchResult {
|
|
45
|
+
matches: KnownDevice[];
|
|
46
|
+
exact: boolean;
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a fingerprint prefix to a single device.
|
|
51
|
+
*
|
|
52
|
+
* @param db - The DevicesDatabase instance
|
|
53
|
+
* @param prefix - The fingerprint prefix (minimum 8 chars)
|
|
54
|
+
* @returns Match result with the matched devices
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolvePrefix(db: DevicesDatabase, prefix: string): PrefixMatchResult;
|
|
57
|
+
/**
|
|
58
|
+
* List all approved devices, marking the current device.
|
|
59
|
+
*
|
|
60
|
+
* @param baseDir - Optional base directory for config/database files
|
|
61
|
+
* @returns List of devices with current-device indicator
|
|
62
|
+
*/
|
|
63
|
+
export declare function listDevicesCommand(baseDir?: string): Promise<DeviceListResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Format the device list for human-readable display.
|
|
66
|
+
*
|
|
67
|
+
* @param result - The list result from listDevicesCommand
|
|
68
|
+
* @returns Formatted string for terminal output
|
|
69
|
+
*/
|
|
70
|
+
export declare function formatDeviceList(result: DeviceListResult): string;
|
|
71
|
+
/**
|
|
72
|
+
* Rename a device by fingerprint prefix.
|
|
73
|
+
*
|
|
74
|
+
* @param prefix - The fingerprint prefix (minimum 8 chars)
|
|
75
|
+
* @param newName - The new device name
|
|
76
|
+
* @param baseDir - Optional base directory
|
|
77
|
+
* @returns Rename result
|
|
78
|
+
*/
|
|
79
|
+
export declare function renameDeviceCommand(prefix: string, newName: string, baseDir?: string): Promise<DeviceRenameResult>;
|
|
80
|
+
/**
|
|
81
|
+
* Remove a device by fingerprint prefix.
|
|
82
|
+
*
|
|
83
|
+
* Safety check: cannot remove the last device if no email is configured
|
|
84
|
+
* (would lock the user out with no recovery path).
|
|
85
|
+
*
|
|
86
|
+
* @param prefix - The fingerprint prefix (minimum 8 chars)
|
|
87
|
+
* @param baseDir - Optional base directory
|
|
88
|
+
* @returns Remove result (caller should handle confirmation UI)
|
|
89
|
+
*/
|
|
90
|
+
export declare function removeDeviceCommand(prefix: string, baseDir?: string): Promise<DeviceRemoveResult>;
|
|
91
|
+
/**
|
|
92
|
+
* Remove all devices except the current one.
|
|
93
|
+
*
|
|
94
|
+
* Nuclear option for security incidents -- removes all approved devices
|
|
95
|
+
* except the device running this command.
|
|
96
|
+
*
|
|
97
|
+
* @param baseDir - Optional base directory
|
|
98
|
+
* @returns Remove-all result
|
|
99
|
+
*/
|
|
100
|
+
export declare function removeAllDevicesCommand(baseDir?: string): Promise<DeviceRemoveAllResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Format an ambiguous prefix match result for display.
|
|
103
|
+
*
|
|
104
|
+
* @param matches - The ambiguous matches
|
|
105
|
+
* @returns Formatted string listing the matches
|
|
106
|
+
*/
|
|
107
|
+
export declare function formatAmbiguousMatches(matches: KnownDevice[]): string;
|
|
108
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/signatures/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAWjE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,yCAAyC;IACzC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,MAAM,GACb,iBAAiB,CA+BnB;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,gBAAgB,CAAC,CAe3B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAqBjE;AAID;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,CAuC7B;AAID;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,CAuD7B;AAID;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAyBhC;AAID;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAQrE"}
|