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,270 @@
|
|
|
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 } from './devices.js';
|
|
17
|
+
import { generateDeviceFingerprint } from './fingerprint.js';
|
|
18
|
+
import { hasVerifiedEmail } from './email-config.js';
|
|
19
|
+
// ─── Constants ──────────────────────────────────────────────────────
|
|
20
|
+
/** Minimum fingerprint prefix length for matching */
|
|
21
|
+
const MIN_PREFIX_LENGTH = 8;
|
|
22
|
+
// ─── Prefix Resolution ─────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a fingerprint prefix to a single device.
|
|
25
|
+
*
|
|
26
|
+
* @param db - The DevicesDatabase instance
|
|
27
|
+
* @param prefix - The fingerprint prefix (minimum 8 chars)
|
|
28
|
+
* @returns Match result with the matched devices
|
|
29
|
+
*/
|
|
30
|
+
export function resolvePrefix(db, prefix) {
|
|
31
|
+
if (prefix.length < MIN_PREFIX_LENGTH) {
|
|
32
|
+
return {
|
|
33
|
+
matches: [],
|
|
34
|
+
exact: false,
|
|
35
|
+
error: `Fingerprint prefix too short (minimum ${MIN_PREFIX_LENGTH} characters). Got ${prefix.length}.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const matches = db.findByFingerprintPrefix(prefix);
|
|
39
|
+
if (matches.length === 0) {
|
|
40
|
+
return {
|
|
41
|
+
matches: [],
|
|
42
|
+
exact: false,
|
|
43
|
+
error: `No device found matching prefix '${prefix}'.`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (matches.length > 1) {
|
|
47
|
+
return {
|
|
48
|
+
matches,
|
|
49
|
+
exact: false,
|
|
50
|
+
error: `Ambiguous prefix '${prefix}' matches ${matches.length} devices. Be more specific.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
matches,
|
|
55
|
+
exact: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// ─── List Devices ───────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* List all approved devices, marking the current device.
|
|
61
|
+
*
|
|
62
|
+
* @param baseDir - Optional base directory for config/database files
|
|
63
|
+
* @returns List of devices with current-device indicator
|
|
64
|
+
*/
|
|
65
|
+
export async function listDevicesCommand(baseDir) {
|
|
66
|
+
const currentFingerprint = generateDeviceFingerprint(baseDir);
|
|
67
|
+
const db = await DevicesDatabase.open(baseDir);
|
|
68
|
+
try {
|
|
69
|
+
const devices = db.listDevices();
|
|
70
|
+
const entries = devices.map((d) => ({
|
|
71
|
+
...d,
|
|
72
|
+
is_current: d.fingerprint === currentFingerprint,
|
|
73
|
+
}));
|
|
74
|
+
return { devices: entries, currentFingerprint };
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
db.close();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Format the device list for human-readable display.
|
|
82
|
+
*
|
|
83
|
+
* @param result - The list result from listDevicesCommand
|
|
84
|
+
* @returns Formatted string for terminal output
|
|
85
|
+
*/
|
|
86
|
+
export function formatDeviceList(result) {
|
|
87
|
+
if (result.devices.length === 0) {
|
|
88
|
+
return 'No approved devices.\n';
|
|
89
|
+
}
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push('Approved devices:\n');
|
|
92
|
+
for (const device of result.devices) {
|
|
93
|
+
const marker = device.is_current ? ' *' : ' ';
|
|
94
|
+
const fpShort = device.fingerprint.slice(0, 12) + '...';
|
|
95
|
+
lines.push(`${marker} ${device.name}`);
|
|
96
|
+
lines.push(` Fingerprint: ${fpShort}`);
|
|
97
|
+
lines.push(` Approved: ${formatDate(device.approved_at)}`);
|
|
98
|
+
lines.push(` Last seen: ${formatDate(device.last_seen_at)}`);
|
|
99
|
+
lines.push(` Method: ${device.approval_method}`);
|
|
100
|
+
lines.push('');
|
|
101
|
+
}
|
|
102
|
+
lines.push(`${result.devices.length} device(s). * = current device`);
|
|
103
|
+
return lines.join('\n') + '\n';
|
|
104
|
+
}
|
|
105
|
+
// ─── Rename Device ──────────────────────────────────────────────────
|
|
106
|
+
/**
|
|
107
|
+
* Rename a device by fingerprint prefix.
|
|
108
|
+
*
|
|
109
|
+
* @param prefix - The fingerprint prefix (minimum 8 chars)
|
|
110
|
+
* @param newName - The new device name
|
|
111
|
+
* @param baseDir - Optional base directory
|
|
112
|
+
* @returns Rename result
|
|
113
|
+
*/
|
|
114
|
+
export async function renameDeviceCommand(prefix, newName, baseDir) {
|
|
115
|
+
const db = await DevicesDatabase.open(baseDir);
|
|
116
|
+
try {
|
|
117
|
+
const match = resolvePrefix(db, prefix);
|
|
118
|
+
if (!match.exact) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
fingerprint: '',
|
|
122
|
+
oldName: '',
|
|
123
|
+
newName,
|
|
124
|
+
error: match.error,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const device = match.matches[0];
|
|
128
|
+
const oldName = device.name;
|
|
129
|
+
const renamed = db.renameDevice(device.fingerprint, newName);
|
|
130
|
+
if (!renamed) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
fingerprint: device.fingerprint,
|
|
134
|
+
oldName,
|
|
135
|
+
newName,
|
|
136
|
+
error: 'Failed to rename device.',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
fingerprint: device.fingerprint,
|
|
142
|
+
oldName,
|
|
143
|
+
newName,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
db.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ─── Remove Device ──────────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Remove a device by fingerprint prefix.
|
|
153
|
+
*
|
|
154
|
+
* Safety check: cannot remove the last device if no email is configured
|
|
155
|
+
* (would lock the user out with no recovery path).
|
|
156
|
+
*
|
|
157
|
+
* @param prefix - The fingerprint prefix (minimum 8 chars)
|
|
158
|
+
* @param baseDir - Optional base directory
|
|
159
|
+
* @returns Remove result (caller should handle confirmation UI)
|
|
160
|
+
*/
|
|
161
|
+
export async function removeDeviceCommand(prefix, baseDir) {
|
|
162
|
+
const db = await DevicesDatabase.open(baseDir);
|
|
163
|
+
try {
|
|
164
|
+
const match = resolvePrefix(db, prefix);
|
|
165
|
+
if (!match.exact) {
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
fingerprint: '',
|
|
169
|
+
name: '',
|
|
170
|
+
wasCurrentDevice: false,
|
|
171
|
+
error: match.error,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const device = match.matches[0];
|
|
175
|
+
const currentFingerprint = generateDeviceFingerprint(baseDir);
|
|
176
|
+
const wasCurrentDevice = device.fingerprint === currentFingerprint;
|
|
177
|
+
// Safety: cannot remove the last device if no email is configured
|
|
178
|
+
if (db.countDevices() === 1 && !hasVerifiedEmail(baseDir)) {
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
fingerprint: device.fingerprint,
|
|
182
|
+
name: device.name,
|
|
183
|
+
wasCurrentDevice,
|
|
184
|
+
error: 'Cannot remove the last device when no email is configured. ' +
|
|
185
|
+
'This would lock you out with no recovery path. ' +
|
|
186
|
+
'Configure an email first: unotoken config email <address>',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const removed = db.removeDevice(device.fingerprint);
|
|
190
|
+
if (!removed) {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
fingerprint: device.fingerprint,
|
|
194
|
+
name: device.name,
|
|
195
|
+
wasCurrentDevice,
|
|
196
|
+
error: 'Failed to remove device.',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
fingerprint: device.fingerprint,
|
|
202
|
+
name: device.name,
|
|
203
|
+
wasCurrentDevice,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
db.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ─── Remove All Devices ─────────────────────────────────────────────
|
|
211
|
+
/**
|
|
212
|
+
* Remove all devices except the current one.
|
|
213
|
+
*
|
|
214
|
+
* Nuclear option for security incidents -- removes all approved devices
|
|
215
|
+
* except the device running this command.
|
|
216
|
+
*
|
|
217
|
+
* @param baseDir - Optional base directory
|
|
218
|
+
* @returns Remove-all result
|
|
219
|
+
*/
|
|
220
|
+
export async function removeAllDevicesCommand(baseDir) {
|
|
221
|
+
const currentFingerprint = generateDeviceFingerprint(baseDir);
|
|
222
|
+
const db = await DevicesDatabase.open(baseDir);
|
|
223
|
+
try {
|
|
224
|
+
// Ensure the current device is actually in the database
|
|
225
|
+
if (!db.isDeviceKnown(currentFingerprint)) {
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
removedCount: 0,
|
|
229
|
+
error: 'Current device is not in the approved devices list. ' +
|
|
230
|
+
'Cannot remove all devices without keeping the current one.',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const removedCount = db.removeAllExcept(currentFingerprint);
|
|
234
|
+
return {
|
|
235
|
+
success: true,
|
|
236
|
+
removedCount,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
db.close();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
244
|
+
/**
|
|
245
|
+
* Format an ambiguous prefix match result for display.
|
|
246
|
+
*
|
|
247
|
+
* @param matches - The ambiguous matches
|
|
248
|
+
* @returns Formatted string listing the matches
|
|
249
|
+
*/
|
|
250
|
+
export function formatAmbiguousMatches(matches) {
|
|
251
|
+
const lines = [];
|
|
252
|
+
lines.push('Multiple devices match this prefix:\n');
|
|
253
|
+
for (const device of matches) {
|
|
254
|
+
lines.push(` ${device.fingerprint.slice(0, 16)}... ${device.name}`);
|
|
255
|
+
}
|
|
256
|
+
lines.push('\nProvide a longer prefix to uniquely identify the device.');
|
|
257
|
+
return lines.join('\n') + '\n';
|
|
258
|
+
}
|
|
259
|
+
function formatDate(isoDate) {
|
|
260
|
+
try {
|
|
261
|
+
const d = new Date(isoDate);
|
|
262
|
+
if (isNaN(d.getTime()))
|
|
263
|
+
return isoDate;
|
|
264
|
+
return d.toLocaleString();
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return isoDate;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/signatures/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known devices registry for unotoken device signatures.
|
|
3
|
+
*
|
|
4
|
+
* Manages a SQLite database of approved devices. Each device is identified
|
|
5
|
+
* by its fingerprint (see fingerprint.ts). Devices must be approved before
|
|
6
|
+
* they can access the vault.
|
|
7
|
+
*
|
|
8
|
+
* The registry is stored at ~/.yokotoken/devices.db (separate from the
|
|
9
|
+
* encrypted vault) because device checks happen BEFORE vault unlock.
|
|
10
|
+
*
|
|
11
|
+
* Table: known_devices
|
|
12
|
+
* - device_id: UUID primary key
|
|
13
|
+
* - fingerprint: SHA-256 hex string (unique)
|
|
14
|
+
* - name: human-friendly device name (e.g., "stefan@macbook")
|
|
15
|
+
* - approved_at: ISO 8601 timestamp
|
|
16
|
+
* - last_seen_at: ISO 8601 timestamp (updated on each vault operation)
|
|
17
|
+
* - approval_method: 'email_code' | 'initial_setup'
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
export type ApprovalMethod = 'email_code' | 'initial_setup';
|
|
22
|
+
export interface KnownDevice {
|
|
23
|
+
device_id: string;
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
name: string;
|
|
26
|
+
approved_at: string;
|
|
27
|
+
last_seen_at: string;
|
|
28
|
+
approval_method: ApprovalMethod;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Manages the known_devices SQLite database.
|
|
32
|
+
*
|
|
33
|
+
* Uses sql.js (SQLite compiled to WASM) for portable, single-file access.
|
|
34
|
+
* The database lives in memory and is persisted to disk after mutations.
|
|
35
|
+
*/
|
|
36
|
+
export declare class DevicesDatabase {
|
|
37
|
+
private db;
|
|
38
|
+
private dbPath;
|
|
39
|
+
private constructor();
|
|
40
|
+
/**
|
|
41
|
+
* Open or create the devices database at the given path.
|
|
42
|
+
*
|
|
43
|
+
* @param baseDir - Optional base directory (default: ~/.yokotoken)
|
|
44
|
+
* @returns A ready-to-use DevicesDatabase instance
|
|
45
|
+
*/
|
|
46
|
+
static open(baseDir?: string): Promise<DevicesDatabase>;
|
|
47
|
+
/**
|
|
48
|
+
* Initialize the known_devices table if it does not exist.
|
|
49
|
+
*/
|
|
50
|
+
private initSchema;
|
|
51
|
+
/**
|
|
52
|
+
* Persist the in-memory database to disk.
|
|
53
|
+
*/
|
|
54
|
+
private save;
|
|
55
|
+
/**
|
|
56
|
+
* Check whether a device with the given fingerprint is registered.
|
|
57
|
+
*
|
|
58
|
+
* @param fingerprint - The device fingerprint (SHA-256 hex)
|
|
59
|
+
* @returns true if the device is in the known_devices table
|
|
60
|
+
*/
|
|
61
|
+
isDeviceKnown(fingerprint: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Register a new device in the known_devices table.
|
|
64
|
+
*
|
|
65
|
+
* @param fingerprint - The device fingerprint (SHA-256 hex)
|
|
66
|
+
* @param name - Human-friendly device name
|
|
67
|
+
* @param method - How the device was approved
|
|
68
|
+
* @returns The newly created KnownDevice record
|
|
69
|
+
* @throws Error if the fingerprint is already registered
|
|
70
|
+
*/
|
|
71
|
+
registerDevice(fingerprint: string, name: string, method: ApprovalMethod): KnownDevice;
|
|
72
|
+
/**
|
|
73
|
+
* Get a known device by fingerprint.
|
|
74
|
+
*
|
|
75
|
+
* @param fingerprint - The device fingerprint (SHA-256 hex)
|
|
76
|
+
* @returns The device record, or null if not found
|
|
77
|
+
*/
|
|
78
|
+
getDevice(fingerprint: string): KnownDevice | null;
|
|
79
|
+
/**
|
|
80
|
+
* Update the last_seen_at timestamp for a device.
|
|
81
|
+
*
|
|
82
|
+
* Called on each vault operation from a known device.
|
|
83
|
+
*
|
|
84
|
+
* @param fingerprint - The device fingerprint
|
|
85
|
+
*/
|
|
86
|
+
updateLastSeen(fingerprint: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* List all known (approved) devices.
|
|
89
|
+
*
|
|
90
|
+
* @returns Array of KnownDevice records, ordered by approved_at ascending
|
|
91
|
+
*/
|
|
92
|
+
listDevices(): KnownDevice[];
|
|
93
|
+
/**
|
|
94
|
+
* Remove a device by fingerprint.
|
|
95
|
+
*
|
|
96
|
+
* @param fingerprint - The device fingerprint
|
|
97
|
+
* @returns true if a device was removed, false if not found
|
|
98
|
+
*/
|
|
99
|
+
removeDevice(fingerprint: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Rename a device by fingerprint.
|
|
102
|
+
*
|
|
103
|
+
* @param fingerprint - The device fingerprint
|
|
104
|
+
* @param newName - The new human-friendly name
|
|
105
|
+
* @returns true if the device was renamed, false if not found
|
|
106
|
+
*/
|
|
107
|
+
renameDevice(fingerprint: string, newName: string): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Find devices whose fingerprint starts with the given prefix.
|
|
110
|
+
*
|
|
111
|
+
* Works like git SHA prefix matching -- the user provides the first N
|
|
112
|
+
* characters of the fingerprint and we find matching devices.
|
|
113
|
+
*
|
|
114
|
+
* @param prefix - The fingerprint prefix (minimum 8 characters recommended)
|
|
115
|
+
* @returns Array of matching KnownDevice records
|
|
116
|
+
*/
|
|
117
|
+
findByFingerprintPrefix(prefix: string): KnownDevice[];
|
|
118
|
+
/**
|
|
119
|
+
* Remove all devices except the one with the given fingerprint.
|
|
120
|
+
*
|
|
121
|
+
* Used as a nuclear option for security incidents -- removes all
|
|
122
|
+
* approved devices except the current one.
|
|
123
|
+
*
|
|
124
|
+
* @param keepFingerprint - The fingerprint to keep
|
|
125
|
+
* @returns The number of devices removed
|
|
126
|
+
*/
|
|
127
|
+
removeAllExcept(keepFingerprint: string): number;
|
|
128
|
+
/**
|
|
129
|
+
* Count total known devices.
|
|
130
|
+
*
|
|
131
|
+
* @returns The number of registered devices
|
|
132
|
+
*/
|
|
133
|
+
countDevices(): number;
|
|
134
|
+
/**
|
|
135
|
+
* Check if this is the very first device (no devices registered yet).
|
|
136
|
+
*
|
|
137
|
+
* Used during initial vault setup to auto-approve the first device
|
|
138
|
+
* without requiring an email code.
|
|
139
|
+
*
|
|
140
|
+
* @returns true if zero devices are registered
|
|
141
|
+
*/
|
|
142
|
+
isFirstDevice(): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Close the database connection.
|
|
145
|
+
*/
|
|
146
|
+
close(): void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if the current device is known and auto-approve the first device.
|
|
150
|
+
*
|
|
151
|
+
* This is the main entry point for device checks:
|
|
152
|
+
* 1. If no devices exist yet (first setup), auto-approves current device
|
|
153
|
+
* 2. If devices exist, checks if current fingerprint is registered
|
|
154
|
+
*
|
|
155
|
+
* @param fingerprint - The current device's fingerprint
|
|
156
|
+
* @param deviceName - Human-friendly name for this device
|
|
157
|
+
* @param baseDir - Optional base directory for the devices database
|
|
158
|
+
* @returns Object with `known` boolean and `autoApproved` boolean
|
|
159
|
+
*/
|
|
160
|
+
export declare function checkDevice(fingerprint: string, deviceName: string, baseDir?: string): Promise<{
|
|
161
|
+
known: boolean;
|
|
162
|
+
autoApproved: boolean;
|
|
163
|
+
device: KnownDevice | null;
|
|
164
|
+
}>;
|
|
165
|
+
//# sourceMappingURL=devices.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../src/signatures/devices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,eAAe,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,cAAc,CAAC;CACjC;AAoBD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAkE;IAC5E,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO;IAQP;;;;;OAKG;WACU,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAuB7D;;OAEG;IACH,OAAO,CAAC,UAAU;IAclB;;OAEG;IACH,OAAO,CAAC,IAAI;IASZ;;;;;OAKG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAc3C;;;;;;;;OAQG;IACH,cAAc,CACZ,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,GACrB,WAAW;IAqBd;;;;;OAKG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAqBlD;;;;;;OAMG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IASzC;;;;OAIG;IACH,WAAW,IAAI,WAAW,EAAE;IAoB5B;;;;;OAKG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAY1C;;;;;;OAMG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAW3D;;;;;;;;OAQG;IACH,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE;IAyBtD;;;;;;;;OAQG;IACH,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM;IAahD;;;;OAIG;IACH,YAAY,IAAI,MAAM;IAWtB;;;;;;;OAOG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAAC,CAsBhF"}
|