@vouchagents/client 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../../src/vault/vault.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@vouchagents/client",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./dist/index.js",
7
+ "./react": "./dist/react/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "typecheck": "tsc --noEmit",
12
+ "test": "vitest run",
13
+ "lint": "tsc --noEmit",
14
+ "clean": "rm -rf dist"
15
+ },
16
+ "dependencies": {
17
+ "@vouchagents/protocol": "0.1.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^25.5.0",
21
+ "typescript": "^5.7.0",
22
+ "vitest": "^3.0.0"
23
+ }
24
+ }
package/src/consent.ts ADDED
@@ -0,0 +1,83 @@
1
+ import {
2
+ createConsentToken,
3
+ canonicalHash,
4
+ SensitiveString,
5
+ type AgentSignupDiscovery,
6
+ } from "@vouchagents/protocol";
7
+ import type { Vault } from "./vault/vault.js";
8
+
9
+ export interface ConsentRequest {
10
+ site: AgentSignupDiscovery["branding"] & { url: string };
11
+ requestedFields: {
12
+ required: Array<{ field: string }>;
13
+ optional?: Array<{ field: string }>;
14
+ };
15
+ nonce: string;
16
+ siteEndpoint: string;
17
+ agentId?: string;
18
+ }
19
+
20
+ export interface ConsentApproved {
21
+ approved: true;
22
+ token: string;
23
+ fields: Record<string, string>;
24
+ }
25
+
26
+ export interface ConsentDenied {
27
+ approved: false;
28
+ reason: "user_denied" | "timeout" | "missing_fields" | "vault_locked";
29
+ }
30
+
31
+ export type ConsentResult = ConsentApproved | ConsentDenied;
32
+
33
+ export class ConsentManager {
34
+ constructor(private vault: Vault) {}
35
+
36
+ /** Request consent for a signup. Checks for matching auto-approve policies first. */
37
+ async requestConsent(request: ConsentRequest): Promise<ConsentResult> {
38
+ if (!this.vault.isUnlocked()) {
39
+ return { approved: false, reason: "vault_locked" };
40
+ }
41
+
42
+ const requiredFieldNames = request.requestedFields.required.map(
43
+ (f) => f.field,
44
+ );
45
+
46
+ // Check for matching auto-approve policy
47
+ const policy = await this.vault.findMatchingPolicy(
48
+ request.site.url,
49
+ requiredFieldNames,
50
+ );
51
+
52
+ // Get the field values
53
+ const fields = await this.vault.getFieldsRaw(requiredFieldNames);
54
+
55
+ // Check all required fields are available
56
+ const missing = requiredFieldNames.filter((f) => !fields[f]);
57
+ if (missing.length > 0) {
58
+ return { approved: false, reason: "missing_fields" };
59
+ }
60
+
61
+ // Create consent token
62
+ const keyPair = this.vault.getKeyPair();
63
+ const token = await createConsentToken({
64
+ userKeyPair: keyPair,
65
+ agentId: request.agentId ?? "unknown-agent",
66
+ audience: request.site.url,
67
+ nonce: request.nonce,
68
+ fields: requiredFieldNames,
69
+ data: fields,
70
+ purpose: "account_creation",
71
+ consentMode: policy ? "pre_authorized" : "explicit",
72
+ siteEndpoint: request.siteEndpoint,
73
+ policyId: policy?.id,
74
+ });
75
+
76
+ // Decrement policy uses if auto-approved
77
+ if (policy && policy.remainingUses !== undefined) {
78
+ policy.remainingUses--;
79
+ }
80
+
81
+ return { approved: true, token, fields };
82
+ }
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ // Vault
2
+ export {
3
+ Vault,
4
+ FileStorage,
5
+ LocalStorageBackend,
6
+ MemoryStorage,
7
+ defaultVaultPath,
8
+ type StorageBackend,
9
+ type VaultData,
10
+ type SignupHistoryEntry,
11
+ type PolicyEntry,
12
+ } from "./vault/index.js";
13
+
14
+ // Consent
15
+ export {
16
+ ConsentManager,
17
+ type ConsentRequest,
18
+ type ConsentResult,
19
+ type ConsentApproved,
20
+ type ConsentDenied,
21
+ } from "./consent.js";
22
+
23
+ // Password
24
+ export { generatePassword, type PasswordPolicy } from "./password.js";
@@ -0,0 +1,79 @@
1
+ import { SensitiveString } from "@vouchagents/protocol";
2
+
3
+ export interface PasswordPolicy {
4
+ minLength?: number;
5
+ requireUppercase?: boolean;
6
+ requireLowercase?: boolean;
7
+ requireNumbers?: boolean;
8
+ requireSymbols?: boolean;
9
+ }
10
+
11
+ const DEFAULT_POLICY: Required<PasswordPolicy> = {
12
+ minLength: 20,
13
+ requireUppercase: true,
14
+ requireLowercase: true,
15
+ requireNumbers: true,
16
+ requireSymbols: true,
17
+ };
18
+
19
+ const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
20
+ const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
21
+ const NUMBERS = "0123456789";
22
+ const SYMBOLS = "!@#$%^&*()-_=+[]{}|;:,.<>?";
23
+
24
+ /** Generate a cryptographically random password meeting the given policy. */
25
+ export function generatePassword(policy?: PasswordPolicy): SensitiveString {
26
+ const p = { ...DEFAULT_POLICY, ...policy };
27
+ const length = Math.max(p.minLength, 12);
28
+
29
+ let charset = "";
30
+ const required: string[] = [];
31
+
32
+ if (p.requireUppercase) {
33
+ charset += UPPERCASE;
34
+ required.push(randomChar(UPPERCASE));
35
+ }
36
+ if (p.requireLowercase) {
37
+ charset += LOWERCASE;
38
+ required.push(randomChar(LOWERCASE));
39
+ }
40
+ if (p.requireNumbers) {
41
+ charset += NUMBERS;
42
+ required.push(randomChar(NUMBERS));
43
+ }
44
+ if (p.requireSymbols) {
45
+ charset += SYMBOLS;
46
+ required.push(randomChar(SYMBOLS));
47
+ }
48
+
49
+ if (charset.length === 0) {
50
+ charset = UPPERCASE + LOWERCASE + NUMBERS;
51
+ }
52
+
53
+ // Fill remaining length with random chars from the full charset
54
+ const remaining = length - required.length;
55
+ const chars = [...required];
56
+ for (let i = 0; i < remaining; i++) {
57
+ chars.push(randomChar(charset));
58
+ }
59
+
60
+ // Shuffle to avoid predictable positions for required chars
61
+ shuffle(chars);
62
+
63
+ return new SensitiveString(chars.join(""));
64
+ }
65
+
66
+ function randomChar(charset: string): string {
67
+ const array = new Uint32Array(1);
68
+ globalThis.crypto.getRandomValues(array);
69
+ return charset[array[0] % charset.length];
70
+ }
71
+
72
+ function shuffle(array: string[]): void {
73
+ for (let i = array.length - 1; i > 0; i--) {
74
+ const randomValues = new Uint32Array(1);
75
+ globalThis.crypto.getRandomValues(randomValues);
76
+ const j = randomValues[0] % (i + 1);
77
+ [array[i], array[j]] = [array[j], array[i]];
78
+ }
79
+ }
@@ -0,0 +1,9 @@
1
+ export { Vault } from "./vault.js";
2
+ export type { VaultData, SignupHistoryEntry, PolicyEntry } from "./vault.js";
3
+ export {
4
+ FileStorage,
5
+ LocalStorageBackend,
6
+ MemoryStorage,
7
+ defaultVaultPath,
8
+ type StorageBackend,
9
+ } from "./storage.js";
@@ -0,0 +1,80 @@
1
+ /** Storage backend interface. The vault doesn't care where the encrypted blob lives. */
2
+ export interface StorageBackend {
3
+ read(): Promise<string | null>;
4
+ write(data: string): Promise<void>;
5
+ exists(): Promise<boolean>;
6
+ }
7
+
8
+ /** File-based storage (~/.agent-signup/vault.json). Works in Node.js/Bun. */
9
+ export class FileStorage implements StorageBackend {
10
+ constructor(private path: string) {}
11
+
12
+ async read(): Promise<string | null> {
13
+ try {
14
+ const fs = await import("node:fs/promises");
15
+ return await fs.readFile(this.path, "utf-8");
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ async write(data: string): Promise<void> {
22
+ const fs = await import("node:fs/promises");
23
+ const path = await import("node:path");
24
+ await fs.mkdir(path.dirname(this.path), { recursive: true });
25
+ await fs.writeFile(this.path, data, "utf-8");
26
+ }
27
+
28
+ async exists(): Promise<boolean> {
29
+ try {
30
+ const fs = await import("node:fs/promises");
31
+ await fs.access(this.path);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ }
38
+
39
+ /** localStorage-based storage. Works in browsers. */
40
+ export class LocalStorageBackend implements StorageBackend {
41
+ constructor(private key: string = "agent-signup:vault") {}
42
+
43
+ async read(): Promise<string | null> {
44
+ return globalThis.localStorage?.getItem(this.key) ?? null;
45
+ }
46
+
47
+ async write(data: string): Promise<void> {
48
+ globalThis.localStorage?.setItem(this.key, data);
49
+ }
50
+
51
+ async exists(): Promise<boolean> {
52
+ return globalThis.localStorage?.getItem(this.key) !== null;
53
+ }
54
+ }
55
+
56
+ /** In-memory storage for testing. */
57
+ export class MemoryStorage implements StorageBackend {
58
+ private data: string | null = null;
59
+
60
+ async read(): Promise<string | null> {
61
+ return this.data;
62
+ }
63
+
64
+ async write(data: string): Promise<void> {
65
+ this.data = data;
66
+ }
67
+
68
+ async exists(): Promise<boolean> {
69
+ return this.data !== null;
70
+ }
71
+ }
72
+
73
+ /** Get the default vault file path. */
74
+ export function defaultVaultPath(): string {
75
+ const home =
76
+ typeof process !== "undefined"
77
+ ? process.env.HOME || process.env.USERPROFILE || "."
78
+ : ".";
79
+ return `${home}/.agent-signup/vault.json`;
80
+ }
@@ -0,0 +1,343 @@
1
+ import {
2
+ generateKeyPair,
3
+ deriveEncryptionKey,
4
+ encrypt,
5
+ decrypt,
6
+ generateSalt,
7
+ base64urlEncode,
8
+ base64urlDecode,
9
+ type KeyPair,
10
+ SensitiveString,
11
+ } from "@vouchagents/protocol";
12
+ import type { StorageBackend } from "./storage.js";
13
+
14
+ export interface VaultData {
15
+ fields: Record<string, string>;
16
+ passwords: Record<string, string>;
17
+ history: SignupHistoryEntry[];
18
+ policies: PolicyEntry[];
19
+ custom: Record<string, string>;
20
+ }
21
+
22
+ export interface SignupHistoryEntry {
23
+ id: string;
24
+ site: string;
25
+ siteUrl: string;
26
+ fields: string[];
27
+ consentMode: "explicit" | "pre_authorized" | "manual";
28
+ status: "pending" | "verified" | "expired" | "failed" | "user_directed";
29
+ timestamp: string;
30
+ }
31
+
32
+ export interface PolicyEntry {
33
+ id: string;
34
+ name: string;
35
+ scopes: string[];
36
+ allowedOrigins: string[];
37
+ allowedFields: string[];
38
+ maxUses?: number;
39
+ remainingUses?: number;
40
+ expiresAt: string;
41
+ createdAt: string;
42
+ }
43
+
44
+ interface VaultFile {
45
+ version: 1;
46
+ kdf: {
47
+ algorithm: "pbkdf2";
48
+ iterations: number;
49
+ salt: string;
50
+ };
51
+ vault: string;
52
+ vault_iv: string;
53
+ signing_public_key: string;
54
+ created_at: string;
55
+ updated_at: string;
56
+ }
57
+
58
+ export class Vault {
59
+ private encryptionKey: CryptoKey | null = null;
60
+ private keyPair: KeyPair | null = null;
61
+ private data: VaultData | null = null;
62
+ private vaultFile: VaultFile | null = null;
63
+ private lockedAt: number = 0;
64
+ private readonly autoLockMs = 15 * 60 * 1000; // 15 minutes
65
+
66
+ private constructor(private storage: StorageBackend) {}
67
+
68
+ /** Create a new vault with a passphrase. */
69
+ static async create(options: {
70
+ passphrase: string;
71
+ storage: StorageBackend;
72
+ }): Promise<Vault> {
73
+ const vault = new Vault(options.storage);
74
+ const salt = generateSalt();
75
+ const encryptionKey = await deriveEncryptionKey(options.passphrase, salt);
76
+ const keyPair = await generateKeyPair();
77
+
78
+ vault.encryptionKey = encryptionKey;
79
+ vault.keyPair = keyPair;
80
+ vault.data = {
81
+ fields: {},
82
+ passwords: {},
83
+ history: [],
84
+ policies: [],
85
+ custom: {},
86
+ };
87
+
88
+ vault.vaultFile = {
89
+ version: 1,
90
+ kdf: {
91
+ algorithm: "pbkdf2",
92
+ iterations: 600_000,
93
+ salt: base64urlEncode(salt),
94
+ },
95
+ vault: "", // filled on save
96
+ vault_iv: "",
97
+ signing_public_key: base64urlEncode(keyPair.publicKey),
98
+ created_at: new Date().toISOString(),
99
+ updated_at: new Date().toISOString(),
100
+ };
101
+
102
+ vault.lockedAt = Date.now();
103
+ await vault.save();
104
+ return vault;
105
+ }
106
+
107
+ /** Open an existing vault with a passphrase. */
108
+ static async open(options: {
109
+ passphrase: string;
110
+ storage: StorageBackend;
111
+ }): Promise<Vault> {
112
+ const vault = new Vault(options.storage);
113
+ const raw = await options.storage.read();
114
+ if (!raw) {
115
+ throw new Error("No vault found");
116
+ }
117
+
118
+ const vaultFile: VaultFile = JSON.parse(raw);
119
+ vault.vaultFile = vaultFile;
120
+
121
+ const salt = base64urlDecode(vaultFile.kdf.salt);
122
+ const encryptionKey = await deriveEncryptionKey(options.passphrase, salt);
123
+ vault.encryptionKey = encryptionKey;
124
+
125
+ // Decrypt the vault data
126
+ const ciphertext = base64urlDecode(vaultFile.vault);
127
+ const iv = base64urlDecode(vaultFile.vault_iv);
128
+
129
+ try {
130
+ const decrypted = await decrypt(ciphertext, encryptionKey, iv);
131
+ const json = new TextDecoder().decode(decrypted);
132
+ const parsed = JSON.parse(json);
133
+ vault.data = parsed.data;
134
+ vault.keyPair = {
135
+ publicKey: base64urlDecode(vaultFile.signing_public_key),
136
+ privateKey: base64urlDecode(parsed.privateKey),
137
+ keyId: parsed.keyId,
138
+ };
139
+ } catch {
140
+ throw new Error("Wrong passphrase");
141
+ }
142
+
143
+ vault.lockedAt = Date.now();
144
+ return vault;
145
+ }
146
+
147
+ /** Check if a vault exists at the storage location. */
148
+ static async exists(storage: StorageBackend): Promise<boolean> {
149
+ return storage.exists();
150
+ }
151
+
152
+ // --- Field operations ---
153
+
154
+ async setField(name: string, value: string): Promise<void> {
155
+ this.ensureUnlocked();
156
+ this.data!.fields[name] = value;
157
+ await this.save();
158
+ }
159
+
160
+ async getField(name: string): Promise<SensitiveString | null> {
161
+ this.ensureUnlocked();
162
+ const value = this.data!.fields[name];
163
+ return value ? new SensitiveString(value) : null;
164
+ }
165
+
166
+ async listFields(): Promise<string[]> {
167
+ this.ensureUnlocked();
168
+ return Object.keys(this.data!.fields);
169
+ }
170
+
171
+ async getFieldsRaw(fieldNames: string[]): Promise<Record<string, string>> {
172
+ this.ensureUnlocked();
173
+ const result: Record<string, string> = {};
174
+ for (const name of fieldNames) {
175
+ const value = this.data!.fields[name] ?? this.data!.custom[name];
176
+ if (value) result[name] = value;
177
+ }
178
+ return result;
179
+ }
180
+
181
+ // --- Password operations ---
182
+
183
+ async setPassword(siteUrl: string, password: SensitiveString): Promise<void> {
184
+ this.ensureUnlocked();
185
+ this.data!.passwords[siteUrl] = password.unsafeUnwrap();
186
+ await this.save();
187
+ }
188
+
189
+ async getPassword(siteUrl: string): Promise<SensitiveString | null> {
190
+ this.ensureUnlocked();
191
+ const value = this.data!.passwords[siteUrl];
192
+ return value ? new SensitiveString(value) : null;
193
+ }
194
+
195
+ // --- Custom fields ---
196
+
197
+ async setCustomField(name: string, value: string): Promise<void> {
198
+ this.ensureUnlocked();
199
+ this.data!.custom[name] = value;
200
+ await this.save();
201
+ }
202
+
203
+ // --- History ---
204
+
205
+ async addHistory(entry: SignupHistoryEntry): Promise<void> {
206
+ this.ensureUnlocked();
207
+ this.data!.history.unshift(entry);
208
+ await this.save();
209
+ }
210
+
211
+ async getHistory(options?: {
212
+ limit?: number;
213
+ siteUrl?: string;
214
+ }): Promise<SignupHistoryEntry[]> {
215
+ this.ensureUnlocked();
216
+ let entries = this.data!.history;
217
+ if (options?.siteUrl) {
218
+ entries = entries.filter((e) => e.siteUrl === options.siteUrl);
219
+ }
220
+ if (options?.limit) {
221
+ entries = entries.slice(0, options.limit);
222
+ }
223
+ return entries;
224
+ }
225
+
226
+ // --- Policies ---
227
+
228
+ async addPolicy(policy: PolicyEntry): Promise<void> {
229
+ this.ensureUnlocked();
230
+ this.data!.policies.push(policy);
231
+ await this.save();
232
+ }
233
+
234
+ async getPolicies(): Promise<PolicyEntry[]> {
235
+ this.ensureUnlocked();
236
+ return this.data!.policies.filter(
237
+ (p) => new Date(p.expiresAt) > new Date(),
238
+ );
239
+ }
240
+
241
+ async removePolicy(policyId: string): Promise<void> {
242
+ this.ensureUnlocked();
243
+ this.data!.policies = this.data!.policies.filter((p) => p.id !== policyId);
244
+ await this.save();
245
+ }
246
+
247
+ async findMatchingPolicy(
248
+ origin: string,
249
+ fields: string[],
250
+ ): Promise<PolicyEntry | null> {
251
+ this.ensureUnlocked();
252
+ const now = new Date();
253
+ return (
254
+ this.data!.policies.find((p) => {
255
+ if (new Date(p.expiresAt) <= now) return false;
256
+ if (p.remainingUses !== undefined && p.remainingUses <= 0) return false;
257
+ const originMatch = p.allowedOrigins.some(
258
+ (o) => o === "*" || origin.includes(o),
259
+ );
260
+ if (!originMatch) return false;
261
+ return fields.every((f) => p.allowedFields.includes(f));
262
+ }) ?? null
263
+ );
264
+ }
265
+
266
+ // --- Identity ---
267
+
268
+ getIdentity(): { publicKey: string; keyId: string } {
269
+ this.ensureUnlocked();
270
+ return {
271
+ publicKey: base64urlEncode(this.keyPair!.publicKey),
272
+ keyId: this.keyPair!.keyId,
273
+ };
274
+ }
275
+
276
+ getKeyPair(): KeyPair {
277
+ this.ensureUnlocked();
278
+ return this.keyPair!;
279
+ }
280
+
281
+ // --- Lock/unlock ---
282
+
283
+ isUnlocked(): boolean {
284
+ if (!this.encryptionKey || !this.data) return false;
285
+ if (Date.now() - this.lockedAt > this.autoLockMs) {
286
+ this.lock();
287
+ return false;
288
+ }
289
+ return true;
290
+ }
291
+
292
+ lock(): void {
293
+ this.encryptionKey = null;
294
+ this.keyPair = null;
295
+ this.data = null;
296
+ }
297
+
298
+ // --- Export/import ---
299
+
300
+ async export(): Promise<string> {
301
+ const raw = await this.storage.read();
302
+ if (!raw) throw new Error("No vault to export");
303
+ return raw;
304
+ }
305
+
306
+ static async import(
307
+ exported: string,
308
+ options: { passphrase: string; storage: StorageBackend },
309
+ ): Promise<Vault> {
310
+ await options.storage.write(exported);
311
+ return Vault.open(options);
312
+ }
313
+
314
+ // --- Internal ---
315
+
316
+ private ensureUnlocked(): void {
317
+ if (!this.isUnlocked()) {
318
+ throw new Error("Vault is locked");
319
+ }
320
+ this.lockedAt = Date.now(); // reset idle timer
321
+ }
322
+
323
+ private async save(): Promise<void> {
324
+ if (!this.encryptionKey || !this.data || !this.keyPair || !this.vaultFile) {
325
+ throw new Error("Cannot save: vault not initialized");
326
+ }
327
+
328
+ const plaintext = JSON.stringify({
329
+ data: this.data,
330
+ privateKey: base64urlEncode(this.keyPair.privateKey),
331
+ keyId: this.keyPair.keyId,
332
+ });
333
+
334
+ const encoded = new TextEncoder().encode(plaintext);
335
+ const { ciphertext, iv } = await encrypt(encoded, this.encryptionKey);
336
+
337
+ this.vaultFile.vault = base64urlEncode(ciphertext);
338
+ this.vaultFile.vault_iv = base64urlEncode(iv);
339
+ this.vaultFile.updated_at = new Date().toISOString();
340
+
341
+ await this.storage.write(JSON.stringify(this.vaultFile, null, 2));
342
+ }
343
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "lib": ["ES2022", "DOM"]
7
+ },
8
+ "include": ["src"]
9
+ }