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.
Files changed (122) hide show
  1. package/README.md +360 -0
  2. package/dist/cli.d.ts +17 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +1207 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/client.d.ts +15 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +15 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/db.d.ts +52 -0
  11. package/dist/db.d.ts.map +1 -0
  12. package/dist/db.js +97 -0
  13. package/dist/db.js.map +1 -0
  14. package/dist/dotenv.d.ts +69 -0
  15. package/dist/dotenv.d.ts.map +1 -0
  16. package/dist/dotenv.js +115 -0
  17. package/dist/dotenv.js.map +1 -0
  18. package/dist/env-mapper.d.ts +55 -0
  19. package/dist/env-mapper.d.ts.map +1 -0
  20. package/dist/env-mapper.js +97 -0
  21. package/dist/env-mapper.js.map +1 -0
  22. package/dist/exec.d.ts +80 -0
  23. package/dist/exec.d.ts.map +1 -0
  24. package/dist/exec.js +214 -0
  25. package/dist/exec.js.map +1 -0
  26. package/dist/index.d.ts +12 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +43 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/oauth/commands.d.ts +151 -0
  31. package/dist/oauth/commands.d.ts.map +1 -0
  32. package/dist/oauth/commands.js +322 -0
  33. package/dist/oauth/commands.js.map +1 -0
  34. package/dist/oauth/config.d.ts +84 -0
  35. package/dist/oauth/config.d.ts.map +1 -0
  36. package/dist/oauth/config.js +156 -0
  37. package/dist/oauth/config.js.map +1 -0
  38. package/dist/oauth/crypto-helpers.d.ts +44 -0
  39. package/dist/oauth/crypto-helpers.d.ts.map +1 -0
  40. package/dist/oauth/crypto-helpers.js +94 -0
  41. package/dist/oauth/crypto-helpers.js.map +1 -0
  42. package/dist/oauth/device-secret.d.ts +57 -0
  43. package/dist/oauth/device-secret.d.ts.map +1 -0
  44. package/dist/oauth/device-secret.js +106 -0
  45. package/dist/oauth/device-secret.js.map +1 -0
  46. package/dist/oauth/flow.d.ts +112 -0
  47. package/dist/oauth/flow.d.ts.map +1 -0
  48. package/dist/oauth/flow.js +255 -0
  49. package/dist/oauth/flow.js.map +1 -0
  50. package/dist/oauth/index.d.ts +18 -0
  51. package/dist/oauth/index.d.ts.map +1 -0
  52. package/dist/oauth/index.js +24 -0
  53. package/dist/oauth/index.js.map +1 -0
  54. package/dist/oauth/key-wrap.d.ts +146 -0
  55. package/dist/oauth/key-wrap.d.ts.map +1 -0
  56. package/dist/oauth/key-wrap.js +275 -0
  57. package/dist/oauth/key-wrap.js.map +1 -0
  58. package/dist/oauth/pkce.d.ts +29 -0
  59. package/dist/oauth/pkce.d.ts.map +1 -0
  60. package/dist/oauth/pkce.js +34 -0
  61. package/dist/oauth/pkce.js.map +1 -0
  62. package/dist/oauth/provider.d.ts +79 -0
  63. package/dist/oauth/provider.d.ts.map +1 -0
  64. package/dist/oauth/provider.js +10 -0
  65. package/dist/oauth/provider.js.map +1 -0
  66. package/dist/oauth/providers/github.d.ts +75 -0
  67. package/dist/oauth/providers/github.d.ts.map +1 -0
  68. package/dist/oauth/providers/github.js +119 -0
  69. package/dist/oauth/providers/github.js.map +1 -0
  70. package/dist/oauth/providers/google.d.ts +115 -0
  71. package/dist/oauth/providers/google.d.ts.map +1 -0
  72. package/dist/oauth/providers/google.js +285 -0
  73. package/dist/oauth/providers/google.js.map +1 -0
  74. package/dist/sdk.d.ts +8 -0
  75. package/dist/sdk.d.ts.map +1 -0
  76. package/dist/sdk.js +8 -0
  77. package/dist/sdk.js.map +1 -0
  78. package/dist/server.d.ts +33 -0
  79. package/dist/server.d.ts.map +1 -0
  80. package/dist/server.js +287 -0
  81. package/dist/server.js.map +1 -0
  82. package/dist/signatures/approval-codes.d.ts +192 -0
  83. package/dist/signatures/approval-codes.d.ts.map +1 -0
  84. package/dist/signatures/approval-codes.js +407 -0
  85. package/dist/signatures/approval-codes.js.map +1 -0
  86. package/dist/signatures/commands.d.ts +108 -0
  87. package/dist/signatures/commands.d.ts.map +1 -0
  88. package/dist/signatures/commands.js +270 -0
  89. package/dist/signatures/commands.js.map +1 -0
  90. package/dist/signatures/devices.d.ts +165 -0
  91. package/dist/signatures/devices.d.ts.map +1 -0
  92. package/dist/signatures/devices.js +344 -0
  93. package/dist/signatures/devices.js.map +1 -0
  94. package/dist/signatures/email-config.d.ts +102 -0
  95. package/dist/signatures/email-config.d.ts.map +1 -0
  96. package/dist/signatures/email-config.js +188 -0
  97. package/dist/signatures/email-config.js.map +1 -0
  98. package/dist/signatures/email.d.ts +106 -0
  99. package/dist/signatures/email.d.ts.map +1 -0
  100. package/dist/signatures/email.js +180 -0
  101. package/dist/signatures/email.js.map +1 -0
  102. package/dist/signatures/fingerprint.d.ts +70 -0
  103. package/dist/signatures/fingerprint.d.ts.map +1 -0
  104. package/dist/signatures/fingerprint.js +123 -0
  105. package/dist/signatures/fingerprint.js.map +1 -0
  106. package/dist/signatures/guard.d.ts +118 -0
  107. package/dist/signatures/guard.d.ts.map +1 -0
  108. package/dist/signatures/guard.js +310 -0
  109. package/dist/signatures/guard.js.map +1 -0
  110. package/dist/signatures/resend.d.ts +84 -0
  111. package/dist/signatures/resend.d.ts.map +1 -0
  112. package/dist/signatures/resend.js +248 -0
  113. package/dist/signatures/resend.js.map +1 -0
  114. package/dist/token-requests.d.ts +80 -0
  115. package/dist/token-requests.d.ts.map +1 -0
  116. package/dist/token-requests.js +201 -0
  117. package/dist/token-requests.js.map +1 -0
  118. package/dist/tokens.d.ts +80 -0
  119. package/dist/tokens.d.ts.map +1 -0
  120. package/dist/tokens.js +150 -0
  121. package/dist/tokens.js.map +1 -0
  122. 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"}