@glinr/theauth 0.4.2

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,795 @@
1
+ import { and, eq } from 'drizzle-orm';
2
+ import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
3
+
4
+ // src/agent/agent.ts
5
+
6
+ // src/crypto/web-crypto.ts
7
+ var HEX_CHARS = "0123456789abcdef";
8
+ function toHex(bytes) {
9
+ let hex = "";
10
+ for (let i = 0; i < bytes.length; i++) {
11
+ const b = bytes[i];
12
+ hex += HEX_CHARS[b >> 4];
13
+ hex += HEX_CHARS[b & 15];
14
+ }
15
+ return hex;
16
+ }
17
+ function toBase64Url(bytes) {
18
+ let binary = "";
19
+ for (let i = 0; i < bytes.length; i++) {
20
+ binary += String.fromCharCode(bytes[i]);
21
+ }
22
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
23
+ }
24
+ function generateId() {
25
+ return globalThis.crypto.randomUUID();
26
+ }
27
+ function randomBytes(length) {
28
+ const bytes = new Uint8Array(length);
29
+ globalThis.crypto.getRandomValues(bytes);
30
+ return bytes;
31
+ }
32
+ var TEXT_ENCODER = new TextEncoder();
33
+ function toBytes(data) {
34
+ if (typeof data === "string") {
35
+ const encoded = TEXT_ENCODER.encode(data);
36
+ return encoded.buffer.slice(
37
+ encoded.byteOffset,
38
+ encoded.byteOffset + encoded.byteLength
39
+ );
40
+ }
41
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
42
+ }
43
+ async function sha256(data) {
44
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", toBytes(data));
45
+ return toHex(new Uint8Array(digest));
46
+ }
47
+ var users = sqliteTable("kavach_users", {
48
+ id: text("id").primaryKey(),
49
+ email: text("email").notNull().unique(),
50
+ name: text("name"),
51
+ username: text("username").unique(),
52
+ externalId: text("external_id"),
53
+ // ID from external auth (better-auth, Auth.js, etc.)
54
+ externalProvider: text("external_provider"),
55
+ // "better-auth", "authjs", "clerk", etc.
56
+ metadata: text("metadata", { mode: "json" }).$type(),
57
+ // Admin ban fields (populated by admin module)
58
+ banned: integer("banned").notNull().default(0),
59
+ banReason: text("ban_reason"),
60
+ banExpiresAt: integer("ban_expires_at", { mode: "timestamp" }),
61
+ forcePasswordReset: integer("force_password_reset").notNull().default(0),
62
+ emailVerified: integer("email_verified").notNull().default(0),
63
+ // Stripe integration fields (populated by kavach-stripe plugin)
64
+ stripeCustomerId: text("stripe_customer_id").unique(),
65
+ stripeSubscriptionId: text("stripe_subscription_id"),
66
+ stripeSubscriptionStatus: text("stripe_subscription_status"),
67
+ stripePriceId: text("stripe_price_id"),
68
+ stripeCurrentPeriodEnd: integer("stripe_current_period_end", { mode: "timestamp" }),
69
+ stripeCancelAtPeriodEnd: integer("stripe_cancel_at_period_end", { mode: "boolean" }).notNull().default(false),
70
+ // Polar integration fields (populated by kavach-polar plugin)
71
+ polarCustomerId: text("polar_customer_id").unique(),
72
+ polarSubscriptionId: text("polar_subscription_id"),
73
+ polarSubscriptionStatus: text("polar_subscription_status"),
74
+ polarProductId: text("polar_product_id"),
75
+ polarCurrentPeriodEnd: integer("polar_current_period_end", { mode: "timestamp" }),
76
+ polarCancelAtPeriodEnd: integer("polar_cancel_at_period_end", { mode: "boolean" }).notNull().default(false),
77
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
78
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
79
+ });
80
+ var tenants = sqliteTable("kavach_tenants", {
81
+ id: text("id").primaryKey(),
82
+ name: text("name").notNull(),
83
+ slug: text("slug").notNull().unique(),
84
+ settings: text("settings", { mode: "json" }).$type(),
85
+ status: text("status", { enum: ["active", "suspended"] }).notNull().default("active"),
86
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
87
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
88
+ });
89
+ var agents = sqliteTable("kavach_agents", {
90
+ id: text("id").primaryKey(),
91
+ ownerId: text("owner_id").notNull().references(() => users.id),
92
+ tenantId: text("tenant_id").references(() => tenants.id),
93
+ // nullable, for multi-tenant scoping
94
+ name: text("name").notNull(),
95
+ type: text("type", { enum: ["autonomous", "delegated", "service"] }).notNull(),
96
+ status: text("status", { enum: ["active", "revoked", "expired"] }).notNull().default("active"),
97
+ tokenHash: text("token_hash").notNull(),
98
+ // hashed agent token
99
+ tokenPrefix: text("token_prefix").notNull(),
100
+ // first 8 chars for identification
101
+ expiresAt: integer("expires_at", { mode: "timestamp" }),
102
+ lastActiveAt: integer("last_active_at", { mode: "timestamp" }),
103
+ metadata: text("metadata", { mode: "json" }).$type(),
104
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
105
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
106
+ });
107
+ var permissions = sqliteTable("kavach_permissions", {
108
+ id: text("id").primaryKey(),
109
+ agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
110
+ resource: text("resource").notNull(),
111
+ // e.g. "mcp:github:*", "tool:file_read"
112
+ actions: text("actions", { mode: "json" }).notNull().$type(),
113
+ // ["read", "write", "execute"]
114
+ constraints: text("constraints", { mode: "json" }).$type(),
115
+ // When set, the policy engine consults the ReBAC graph for this permission.
116
+ relation: text("relation"),
117
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
118
+ });
119
+ sqliteTable("kavach_delegation_chains", {
120
+ id: text("id").primaryKey(),
121
+ fromAgentId: text("from_agent_id").notNull().references(() => agents.id),
122
+ toAgentId: text("to_agent_id").notNull().references(() => agents.id),
123
+ permissions: text("permissions", { mode: "json" }).notNull().$type(),
124
+ depth: integer("depth").notNull().default(1),
125
+ maxDepth: integer("max_depth").notNull().default(3),
126
+ status: text("status", { enum: ["active", "revoked", "expired"] }).notNull().default("active"),
127
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
128
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
129
+ });
130
+ sqliteTable("kavach_audit_logs", {
131
+ id: text("id").primaryKey(),
132
+ agentId: text("agent_id").notNull().references(() => agents.id),
133
+ userId: text("user_id").notNull().references(() => users.id),
134
+ action: text("action").notNull(),
135
+ // "execute", "read", "write", "delete"
136
+ resource: text("resource").notNull(),
137
+ // "mcp:github:create_issue"
138
+ parameters: text("parameters", { mode: "json" }).$type(),
139
+ result: text("result", { enum: ["allowed", "denied", "rate_limited"] }).notNull(),
140
+ reason: text("reason"),
141
+ // why denied/rate_limited
142
+ durationMs: integer("duration_ms").notNull(),
143
+ tokensCost: integer("tokens_cost"),
144
+ ip: text("ip"),
145
+ userAgent: text("user_agent"),
146
+ // True when this audit row corresponds to a policy-engine cache-hit evaluation.
147
+ cacheHit: integer("cache_hit", { mode: "boolean" }).notNull().default(false),
148
+ timestamp: integer("timestamp", { mode: "timestamp" }).notNull()
149
+ });
150
+ sqliteTable("kavach_rate_limits", {
151
+ id: text("id").primaryKey(),
152
+ agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
153
+ resource: text("resource").notNull(),
154
+ windowStart: integer("window_start", { mode: "timestamp" }).notNull(),
155
+ count: integer("count").notNull().default(0)
156
+ });
157
+ sqliteTable("kavach_mcp_servers", {
158
+ id: text("id").primaryKey(),
159
+ name: text("name").notNull(),
160
+ endpoint: text("endpoint").notNull().unique(),
161
+ tools: text("tools", { mode: "json" }).notNull().$type(),
162
+ authRequired: integer("auth_required", { mode: "boolean" }).notNull().default(true),
163
+ rateLimitRpm: integer("rate_limit_rpm"),
164
+ status: text("status", { enum: ["active", "inactive"] }).notNull().default("active"),
165
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
166
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
167
+ });
168
+ sqliteTable("kavach_sessions", {
169
+ id: text("id").primaryKey(),
170
+ userId: text("user_id").notNull().references(() => users.id),
171
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
172
+ metadata: text("metadata", { mode: "json" }).$type(),
173
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
174
+ });
175
+ var oauthClients = sqliteTable("kavach_oauth_clients", {
176
+ id: text("id").primaryKey(),
177
+ clientId: text("client_id").notNull().unique(),
178
+ clientSecret: text("client_secret"),
179
+ // null for public clients
180
+ clientName: text("client_name"),
181
+ clientUri: text("client_uri"),
182
+ redirectUris: text("redirect_uris", { mode: "json" }).notNull().$type(),
183
+ grantTypes: text("grant_types", { mode: "json" }).notNull().$type().default(["authorization_code"]),
184
+ responseTypes: text("response_types", { mode: "json" }).notNull().$type().default(["code"]),
185
+ tokenEndpointAuthMethod: text("token_endpoint_auth_method").notNull().default("client_secret_basic"),
186
+ type: text("type", { enum: ["public", "confidential"] }).notNull().default("confidential"),
187
+ disabled: integer("disabled", { mode: "boolean" }).notNull().default(false),
188
+ metadata: text("metadata", { mode: "json" }).$type(),
189
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
190
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
191
+ });
192
+ sqliteTable("kavach_oauth_access_tokens", {
193
+ id: text("id").primaryKey(),
194
+ accessToken: text("access_token").notNull().unique(),
195
+ refreshToken: text("refresh_token").unique(),
196
+ clientId: text("client_id").notNull().references(() => oauthClients.clientId),
197
+ userId: text("user_id").notNull().references(() => users.id),
198
+ scopes: text("scopes").notNull(),
199
+ // space-separated
200
+ resource: text("resource"),
201
+ // RFC 8707 - audience binding
202
+ accessTokenExpiresAt: integer("access_token_expires_at", { mode: "timestamp" }).notNull(),
203
+ refreshTokenExpiresAt: integer("refresh_token_expires_at", { mode: "timestamp" }),
204
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
205
+ });
206
+ sqliteTable("kavach_oauth_authorization_codes", {
207
+ id: text("id").primaryKey(),
208
+ code: text("code").notNull().unique(),
209
+ clientId: text("client_id").notNull().references(() => oauthClients.clientId),
210
+ userId: text("user_id").notNull().references(() => users.id),
211
+ redirectUri: text("redirect_uri").notNull(),
212
+ scopes: text("scopes").notNull(),
213
+ codeChallenge: text("code_challenge"),
214
+ // PKCE
215
+ codeChallengeMethod: text("code_challenge_method"),
216
+ // "S256"
217
+ resource: text("resource"),
218
+ // RFC 8707
219
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
220
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
221
+ });
222
+ sqliteTable("kavach_budget_policies", {
223
+ id: text("id").primaryKey(),
224
+ agentId: text("agent_id").references(() => agents.id, { onDelete: "cascade" }),
225
+ // nullable
226
+ userId: text("user_id").references(() => users.id),
227
+ // nullable
228
+ tenantId: text("tenant_id").references(() => tenants.id),
229
+ // nullable
230
+ limits: text("limits", { mode: "json" }).notNull().$type(),
231
+ currentUsage: text("current_usage", { mode: "json" }).notNull().$type(),
232
+ action: text("action", { enum: ["warn", "throttle", "block", "revoke"] }).notNull().default("warn"),
233
+ status: text("status", { enum: ["active", "triggered", "disabled"] }).notNull().default("active"),
234
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
235
+ });
236
+ sqliteTable("kavach_agent_cards", {
237
+ id: text("id").primaryKey(),
238
+ agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
239
+ name: text("name").notNull(),
240
+ description: text("description"),
241
+ version: text("version").notNull(),
242
+ protocols: text("protocols", { mode: "json" }).notNull().$type(),
243
+ capabilities: text("capabilities", { mode: "json" }).notNull().$type(),
244
+ authRequirements: text("auth_requirements", { mode: "json" }).notNull().$type(),
245
+ endpoint: text("endpoint"),
246
+ metadata: text("metadata", { mode: "json" }).$type(),
247
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
248
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
249
+ });
250
+ sqliteTable("kavach_approval_requests", {
251
+ id: text("id").primaryKey(),
252
+ agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
253
+ userId: text("user_id").notNull().references(() => users.id),
254
+ action: text("action").notNull(),
255
+ resource: text("resource").notNull(),
256
+ arguments: text("arguments", { mode: "json" }).$type(),
257
+ status: text("status", { enum: ["pending", "approved", "denied", "expired"] }).notNull().default("pending"),
258
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
259
+ respondedAt: integer("responded_at", { mode: "timestamp" }),
260
+ respondedBy: text("responded_by"),
261
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
262
+ });
263
+ sqliteTable("kavach_trust_scores", {
264
+ agentId: text("agent_id").primaryKey().references(() => agents.id, { onDelete: "cascade" }),
265
+ score: integer("score").notNull(),
266
+ level: text("level", {
267
+ enum: ["untrusted", "limited", "standard", "trusted", "elevated"]
268
+ }).notNull(),
269
+ factors: text("factors", { mode: "json" }).notNull().$type(),
270
+ computedAt: integer("computed_at", { mode: "timestamp" }).notNull()
271
+ });
272
+ sqliteTable("kavach_magic_links", {
273
+ id: text("id").primaryKey(),
274
+ email: text("email").notNull(),
275
+ token: text("token").notNull().unique(),
276
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
277
+ used: integer("used", { mode: "boolean" }).notNull().default(false),
278
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
279
+ });
280
+ sqliteTable("kavach_email_otps", {
281
+ id: text("id").primaryKey(),
282
+ email: text("email").notNull(),
283
+ codeHash: text("code_hash").notNull(),
284
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
285
+ attempts: integer("attempts").notNull().default(0),
286
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
287
+ });
288
+ sqliteTable("kavach_totp", {
289
+ userId: text("user_id").primaryKey().references(() => users.id),
290
+ secret: text("secret").notNull(),
291
+ // base32-encoded TOTP secret
292
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
293
+ backupCodes: text("backup_codes", { mode: "json" }).notNull().$type(),
294
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
295
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
296
+ });
297
+ var organizations = sqliteTable("kavach_organizations", {
298
+ id: text("id").primaryKey(),
299
+ name: text("name").notNull(),
300
+ slug: text("slug").notNull().unique(),
301
+ ownerId: text("owner_id").notNull().references(() => users.id),
302
+ metadata: text("metadata", { mode: "json" }).$type(),
303
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
304
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
305
+ });
306
+ sqliteTable("kavach_org_members", {
307
+ id: text("id").primaryKey(),
308
+ orgId: text("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
309
+ userId: text("user_id").notNull().references(() => users.id),
310
+ role: text("role").notNull().default("member"),
311
+ joinedAt: integer("joined_at", { mode: "timestamp" }).notNull()
312
+ });
313
+ sqliteTable("kavach_org_invitations", {
314
+ id: text("id").primaryKey(),
315
+ orgId: text("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
316
+ email: text("email").notNull(),
317
+ role: text("role").notNull().default("member"),
318
+ invitedBy: text("invited_by").notNull().references(() => users.id),
319
+ status: text("status", { enum: ["pending", "accepted", "expired"] }).notNull().default("pending"),
320
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
321
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
322
+ });
323
+ sqliteTable("kavach_org_roles", {
324
+ id: text("id").primaryKey(),
325
+ orgId: text("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
326
+ name: text("name").notNull(),
327
+ permissions: text("permissions", { mode: "json" }).notNull().$type()
328
+ });
329
+ sqliteTable("kavach_passkey_credentials", {
330
+ id: text("id").primaryKey(),
331
+ userId: text("user_id").notNull().references(() => users.id),
332
+ credentialId: text("credential_id").notNull().unique(),
333
+ publicKey: text("public_key").notNull(),
334
+ // base64url-encoded COSE key
335
+ counter: integer("counter").notNull().default(0),
336
+ deviceName: text("device_name"),
337
+ transports: text("transports"),
338
+ // JSON array, e.g. '["internal","usb"]'
339
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
340
+ lastUsedAt: integer("last_used_at", { mode: "timestamp" }).notNull()
341
+ });
342
+ sqliteTable("kavach_sso_connections", {
343
+ id: text("id").primaryKey(),
344
+ orgId: text("org_id").notNull(),
345
+ providerId: text("provider_id").notNull(),
346
+ type: text("type", { enum: ["saml", "oidc"] }).notNull(),
347
+ domain: text("domain").notNull().unique(),
348
+ enabled: integer("enabled").notNull().default(1),
349
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
350
+ });
351
+ sqliteTable("kavach_api_keys", {
352
+ id: text("id").primaryKey(),
353
+ userId: text("user_id").notNull().references(() => users.id),
354
+ name: text("name").notNull(),
355
+ keyHash: text("key_hash").notNull(),
356
+ keyPrefix: text("key_prefix").notNull(),
357
+ permissions: text("permissions", { mode: "json" }).notNull().$type(),
358
+ expiresAt: integer("expires_at", { mode: "timestamp" }),
359
+ lastUsedAt: integer("last_used_at", { mode: "timestamp" }),
360
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
361
+ });
362
+ sqliteTable("kavach_passkey_challenges", {
363
+ id: text("id").primaryKey(),
364
+ challenge: text("challenge").notNull().unique(),
365
+ userId: text("user_id"),
366
+ // null for discoverable credential flows
367
+ type: text("type", { enum: ["registration", "authentication"] }).notNull(),
368
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
369
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
370
+ });
371
+ sqliteTable("kavach_username_accounts", {
372
+ id: text("id").primaryKey(),
373
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
374
+ username: text("username").notNull().unique(),
375
+ passwordHash: text("password_hash").notNull(),
376
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
377
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
378
+ });
379
+ sqliteTable("kavach_phone_verifications", {
380
+ id: text("id").primaryKey(),
381
+ phoneNumber: text("phone_number").notNull(),
382
+ codeHash: text("code_hash").notNull(),
383
+ attempts: integer("attempts").notNull().default(0),
384
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
385
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
386
+ });
387
+ sqliteTable("kavach_trusted_devices", {
388
+ id: text("id").primaryKey(),
389
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
390
+ fingerprint: text("fingerprint").notNull(),
391
+ // HMAC-SHA256 of stable request headers
392
+ label: text("label").notNull(),
393
+ // human-readable, e.g. "Mac", "iPhone"
394
+ trustedAt: integer("trusted_at", { mode: "timestamp" }).notNull(),
395
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull()
396
+ });
397
+ sqliteTable("kavach_one_time_tokens", {
398
+ id: text("id").primaryKey(),
399
+ tokenHash: text("token_hash").notNull().unique(),
400
+ // SHA-256 hex of the raw token
401
+ purpose: text("purpose", {
402
+ enum: ["email-verify", "password-reset", "invitation", "custom"]
403
+ }).notNull(),
404
+ identifier: text("identifier").notNull(),
405
+ // email, userId, or any caller-supplied key
406
+ metadata: text("metadata", { mode: "json" }).$type(),
407
+ used: integer("used", { mode: "boolean" }).notNull().default(false),
408
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
409
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
410
+ });
411
+ sqliteTable("kavach_login_history", {
412
+ id: text("id").primaryKey(),
413
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
414
+ method: text("method").notNull(),
415
+ // LoginMethod — kept as text to support oauth:{provider} variants
416
+ ip: text("ip"),
417
+ userAgent: text("user_agent"),
418
+ timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull()
419
+ });
420
+ sqliteTable("kavach_agent_dids", {
421
+ agentId: text("agent_id").primaryKey().references(() => agents.id, { onDelete: "cascade" }),
422
+ did: text("did").notNull().unique(),
423
+ method: text("method", { enum: ["key", "web"] }).notNull(),
424
+ publicKeyJwk: text("public_key_jwk").notNull(),
425
+ // JSON-serialised JWK (public key only)
426
+ didDocument: text("did_document").notNull(),
427
+ // JSON-serialised DID Document
428
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
429
+ });
430
+ sqliteTable("kavach_oidc_clients", {
431
+ id: text("id").primaryKey(),
432
+ clientId: text("client_id").notNull().unique(),
433
+ clientSecretHash: text("client_secret_hash").notNull(),
434
+ // SHA-256 hex of the raw secret
435
+ clientName: text("client_name").notNull(),
436
+ redirectUris: text("redirect_uris", { mode: "json" }).notNull().$type(),
437
+ grantTypes: text("grant_types", { mode: "json" }).notNull().$type(),
438
+ responseTypes: text("response_types", { mode: "json" }).notNull().$type(),
439
+ scopes: text("scopes", { mode: "json" }).notNull().$type(),
440
+ tokenEndpointAuthMethod: text("token_endpoint_auth_method").notNull().default("client_secret_post"),
441
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
442
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
443
+ });
444
+ sqliteTable("kavach_oidc_auth_codes", {
445
+ id: text("id").primaryKey(),
446
+ codeHash: text("code_hash").notNull().unique(),
447
+ // SHA-256 hex of the raw code
448
+ clientId: text("client_id").notNull(),
449
+ userId: text("user_id").notNull(),
450
+ redirectUri: text("redirect_uri").notNull(),
451
+ scopes: text("scopes").notNull(),
452
+ // space-separated
453
+ nonce: text("nonce"),
454
+ codeChallenge: text("code_challenge"),
455
+ // PKCE S256
456
+ codeChallengeMethod: text("code_challenge_method"),
457
+ used: integer("used", { mode: "boolean" }).notNull().default(false),
458
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
459
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
460
+ });
461
+ sqliteTable("kavach_oidc_refresh_tokens", {
462
+ id: text("id").primaryKey(),
463
+ tokenHash: text("token_hash").notNull().unique(),
464
+ // SHA-256 hex of the raw token
465
+ clientId: text("client_id").notNull(),
466
+ userId: text("user_id").notNull(),
467
+ scopes: text("scopes").notNull(),
468
+ // space-separated
469
+ revoked: integer("revoked", { mode: "boolean" }).notNull().default(false),
470
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
471
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
472
+ });
473
+ sqliteTable("kavach_cost_events", {
474
+ id: text("id").primaryKey(),
475
+ agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
476
+ tool: text("tool").notNull(),
477
+ // e.g. 'openai:gpt-4o', 'anthropic:claude-3-5-sonnet', 'mcp:github'
478
+ inputTokens: integer("input_tokens"),
479
+ outputTokens: integer("output_tokens"),
480
+ /** Cost stored as integer microdollars (costUsd × 1_000_000) to avoid float drift */
481
+ costMicros: integer("cost_micros").notNull(),
482
+ currency: text("currency").notNull().default("USD"),
483
+ metadata: text("metadata", { mode: "json" }).$type(),
484
+ delegationChainId: text("delegation_chain_id"),
485
+ // null when not part of a chain
486
+ recordedAt: integer("recorded_at", { mode: "timestamp" }).notNull()
487
+ });
488
+ sqliteTable("kavach_ephemeral_sessions", {
489
+ id: text("id").primaryKey(),
490
+ agentId: text("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
491
+ ownerId: text("owner_id").notNull().references(() => users.id),
492
+ tokenHash: text("token_hash").notNull().unique(),
493
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
494
+ maxActions: integer("max_actions"),
495
+ // null = unlimited
496
+ actionsUsed: integer("actions_used").notNull().default(0),
497
+ status: text("status", { enum: ["active", "expired", "exhausted", "revoked"] }).notNull().default("active"),
498
+ auditGroupId: text("audit_group_id").notNull(),
499
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
500
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
501
+ });
502
+ sqliteTable("kavach_stream_events", {
503
+ id: text("id").primaryKey(),
504
+ type: text("type").notNull(),
505
+ timestamp: integer("timestamp", { mode: "timestamp" }).notNull(),
506
+ data: text("data", { mode: "json" }).notNull().$type(),
507
+ agentId: text("agent_id"),
508
+ userId: text("user_id")
509
+ });
510
+ sqliteTable("kavach_jwt_refresh_tokens", {
511
+ id: text("id").primaryKey(),
512
+ /** SHA-256 hex of the raw refresh token. The raw token is never stored. */
513
+ tokenHash: text("token_hash").notNull().unique(),
514
+ /** The user who owns this session. */
515
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
516
+ /** True once the token has been used in a refresh or explicit revocation. */
517
+ used: integer("used", { mode: "boolean" }).notNull().default(false),
518
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
519
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
520
+ });
521
+ sqliteTable("kavach_rebac_resources", {
522
+ id: text("id").notNull().primaryKey(),
523
+ type: text("type").notNull(),
524
+ // 'org', 'workspace', 'project', 'document', etc.
525
+ parentId: text("parent_id"),
526
+ parentType: text("parent_type"),
527
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
528
+ });
529
+ sqliteTable("kavach_rebac_relationships", {
530
+ id: text("id").primaryKey(),
531
+ subjectType: text("subject_type").notNull(),
532
+ // 'user', 'agent', 'team', 'role'
533
+ subjectId: text("subject_id").notNull(),
534
+ relation: text("relation").notNull(),
535
+ // 'owner', 'editor', 'viewer', 'member', 'parent'
536
+ objectType: text("object_type").notNull(),
537
+ objectId: text("object_id").notNull(),
538
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
539
+ });
540
+ sqliteTable("kavach_federation_instances", {
541
+ id: text("id").primaryKey(),
542
+ instanceId: text("instance_id").notNull().unique(),
543
+ instanceUrl: text("instance_url").notNull(),
544
+ publicKeyJwk: text("public_key_jwk"),
545
+ // JSON-serialised JWK (public key only)
546
+ trustLevel: text("trust_level", { enum: ["full", "limited", "verify-only"] }).notNull().default("verify-only"),
547
+ discoveredAt: integer("discovered_at", { mode: "timestamp" }),
548
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
549
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
550
+ });
551
+ sqliteTable("kavach_federation_tokens", {
552
+ id: text("id").primaryKey(),
553
+ tokenJti: text("token_jti").notNull().unique(),
554
+ // JWT ID for dedup
555
+ agentId: text("agent_id").notNull(),
556
+ sourceInstanceId: text("source_instance_id").notNull(),
557
+ targetInstanceId: text("target_instance_id"),
558
+ direction: text("direction", { enum: ["issued", "received"] }).notNull(),
559
+ permissions: text("permissions", { mode: "json" }).notNull().$type(),
560
+ trustScore: integer("trust_score"),
561
+ // stored as integer 0-100
562
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
563
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
564
+ });
565
+ var refreshTokenFamilies = sqliteTable("kavach_refresh_token_families", {
566
+ id: text("id").primaryKey(),
567
+ userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
568
+ /** Absolute session expiry — no rotation can extend beyond this date. */
569
+ absoluteExpiresAt: integer("absolute_expires_at", { mode: "timestamp" }).notNull(),
570
+ /** 0 = active, 1 = revoked (reuse detection or explicit logout). */
571
+ revoked: integer("revoked").notNull().default(0),
572
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
573
+ });
574
+ sqliteTable("kavach_refresh_tokens", {
575
+ id: text("id").primaryKey(),
576
+ familyId: text("family_id").notNull().references(() => refreshTokenFamilies.id, { onDelete: "cascade" }),
577
+ /** SHA-256 hash of the opaque token — never store the raw token. */
578
+ tokenHash: text("token_hash").notNull().unique(),
579
+ /** 0 = unused, 1 = already consumed (one-time use). */
580
+ used: integer("used").notNull().default(0),
581
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
582
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull()
583
+ });
584
+
585
+ // src/agent/agent.ts
586
+ async function generateAgentToken() {
587
+ const tokenBytes = randomBytes(32);
588
+ const token = `kv_${toBase64Url(tokenBytes)}`;
589
+ const hash = await sha256(token);
590
+ const prefix = token.slice(0, 11);
591
+ return { token, hash, prefix };
592
+ }
593
+ function parseTokenExpiry(expiry) {
594
+ const now = Date.now();
595
+ const match = expiry.match(/^(\d+)([smhd])$/);
596
+ if (!match) {
597
+ throw new Error(`Invalid token expiry format: ${expiry}. Use format like "24h", "7d", "30m".`);
598
+ }
599
+ const value = Number.parseInt(match[1], 10);
600
+ const unit = match[2];
601
+ const multipliers = {
602
+ s: 1e3,
603
+ m: 60 * 1e3,
604
+ h: 60 * 60 * 1e3,
605
+ d: 24 * 60 * 60 * 1e3
606
+ };
607
+ return new Date(now + value * (multipliers[unit] ?? 0));
608
+ }
609
+ function createAgentModule(config) {
610
+ const { db, maxPerUser, tokenExpiry } = config;
611
+ async function create(input) {
612
+ const existing = await db.select().from(agents).where(and(eq(agents.ownerId, input.ownerId), eq(agents.status, "active")));
613
+ if (existing.length >= maxPerUser) {
614
+ throw new Error(
615
+ `User ${input.ownerId} has reached the maximum of ${maxPerUser} active agents.`
616
+ );
617
+ }
618
+ const id = generateId();
619
+ const { token, hash, prefix } = await generateAgentToken();
620
+ const now = /* @__PURE__ */ new Date();
621
+ const expires = input.expiresAt ?? parseTokenExpiry(tokenExpiry);
622
+ await db.insert(agents).values({
623
+ id,
624
+ ownerId: input.ownerId,
625
+ tenantId: input.tenantId ?? null,
626
+ name: input.name,
627
+ type: input.type,
628
+ status: "active",
629
+ tokenHash: hash,
630
+ tokenPrefix: prefix,
631
+ expiresAt: expires,
632
+ metadata: input.metadata ?? {},
633
+ createdAt: now,
634
+ updatedAt: now
635
+ });
636
+ if (input.permissions.length > 0) {
637
+ await db.insert(permissions).values(
638
+ input.permissions.map((p) => ({
639
+ id: generateId(),
640
+ agentId: id,
641
+ resource: p.resource,
642
+ actions: p.actions,
643
+ constraints: p.constraints ?? null,
644
+ createdAt: now
645
+ }))
646
+ );
647
+ }
648
+ return {
649
+ id,
650
+ ownerId: input.ownerId,
651
+ tenantId: input.tenantId,
652
+ name: input.name,
653
+ type: input.type,
654
+ token,
655
+ permissions: input.permissions,
656
+ status: "active",
657
+ expiresAt: expires,
658
+ createdAt: now,
659
+ updatedAt: now
660
+ };
661
+ }
662
+ async function get(agentId) {
663
+ const rows = await db.select().from(agents).where(eq(agents.id, agentId)).limit(1);
664
+ const agent = rows[0];
665
+ if (!agent) return null;
666
+ const perms = await db.select().from(permissions).where(eq(permissions.agentId, agentId));
667
+ return {
668
+ id: agent.id,
669
+ ownerId: agent.ownerId,
670
+ tenantId: agent.tenantId ?? void 0,
671
+ name: agent.name,
672
+ type: agent.type,
673
+ token: "",
674
+ // never return token after creation
675
+ permissions: perms.map(toPermission),
676
+ status: agent.status,
677
+ expiresAt: agent.expiresAt,
678
+ createdAt: agent.createdAt,
679
+ updatedAt: agent.updatedAt
680
+ };
681
+ }
682
+ async function list(filter) {
683
+ let query = db.select().from(agents).$dynamic();
684
+ const conditions = [];
685
+ if (filter?.userId) conditions.push(eq(agents.ownerId, filter.userId));
686
+ if (filter?.tenantId) conditions.push(eq(agents.tenantId, filter.tenantId));
687
+ if (filter?.status) conditions.push(eq(agents.status, filter.status));
688
+ if (filter?.type) conditions.push(eq(agents.type, filter.type));
689
+ if (conditions.length > 0) {
690
+ query = query.where(and(...conditions));
691
+ }
692
+ const rows = await query;
693
+ const agentIds = rows.map((r) => r.id);
694
+ const permsByAgent = /* @__PURE__ */ new Map();
695
+ for (const id of agentIds) {
696
+ const perms = await db.select().from(permissions).where(eq(permissions.agentId, id));
697
+ permsByAgent.set(id, perms.map(toPermission));
698
+ }
699
+ return rows.map((agent) => ({
700
+ id: agent.id,
701
+ ownerId: agent.ownerId,
702
+ tenantId: agent.tenantId ?? void 0,
703
+ name: agent.name,
704
+ type: agent.type,
705
+ token: "",
706
+ permissions: permsByAgent.get(agent.id) ?? [],
707
+ status: agent.status,
708
+ expiresAt: agent.expiresAt,
709
+ createdAt: agent.createdAt,
710
+ updatedAt: agent.updatedAt
711
+ }));
712
+ }
713
+ async function update(agentId, input) {
714
+ const existing = await get(agentId);
715
+ if (!existing) throw new Error(`Agent ${agentId} not found.`);
716
+ const now = /* @__PURE__ */ new Date();
717
+ await db.update(agents).set({
718
+ name: input.name ?? existing.name,
719
+ expiresAt: input.expiresAt ?? existing.expiresAt,
720
+ metadata: input.metadata,
721
+ updatedAt: now
722
+ }).where(eq(agents.id, agentId));
723
+ if (input.permissions) {
724
+ await db.delete(permissions).where(eq(permissions.agentId, agentId));
725
+ if (input.permissions.length > 0) {
726
+ await db.insert(permissions).values(
727
+ input.permissions.map((p) => ({
728
+ id: generateId(),
729
+ agentId,
730
+ resource: p.resource,
731
+ actions: p.actions,
732
+ constraints: p.constraints ?? null,
733
+ createdAt: now
734
+ }))
735
+ );
736
+ }
737
+ }
738
+ const updated = await get(agentId);
739
+ if (!updated) throw new Error(`Agent ${agentId} disappeared after update.`);
740
+ return updated;
741
+ }
742
+ async function revoke(agentId) {
743
+ const existing = await get(agentId);
744
+ if (!existing) throw new Error(`Agent ${agentId} not found.`);
745
+ await db.update(agents).set({ status: "revoked", updatedAt: /* @__PURE__ */ new Date() }).where(eq(agents.id, agentId));
746
+ }
747
+ async function rotate(agentId) {
748
+ const existing = await get(agentId);
749
+ if (!existing) throw new Error(`Agent ${agentId} not found.`);
750
+ if (existing.status !== "active")
751
+ throw new Error(`Cannot rotate token for ${existing.status} agent.`);
752
+ const { token, hash, prefix } = await generateAgentToken();
753
+ const now = /* @__PURE__ */ new Date();
754
+ await db.update(agents).set({ tokenHash: hash, tokenPrefix: prefix, updatedAt: now }).where(eq(agents.id, agentId));
755
+ return { ...existing, token, updatedAt: now };
756
+ }
757
+ async function validateToken(token) {
758
+ const hash = await sha256(token);
759
+ const rows = await db.select().from(agents).where(eq(agents.tokenHash, hash)).limit(1);
760
+ const agent = rows[0];
761
+ if (!agent) return null;
762
+ if (agent.status !== "active") return null;
763
+ if (agent.expiresAt && agent.expiresAt < /* @__PURE__ */ new Date()) {
764
+ await db.update(agents).set({ status: "expired", updatedAt: /* @__PURE__ */ new Date() }).where(eq(agents.id, agent.id));
765
+ return null;
766
+ }
767
+ await db.update(agents).set({ lastActiveAt: /* @__PURE__ */ new Date() }).where(eq(agents.id, agent.id));
768
+ const perms = await db.select().from(permissions).where(eq(permissions.agentId, agent.id));
769
+ return {
770
+ id: agent.id,
771
+ ownerId: agent.ownerId,
772
+ tenantId: agent.tenantId ?? void 0,
773
+ name: agent.name,
774
+ type: agent.type,
775
+ token: "",
776
+ permissions: perms.map(toPermission),
777
+ status: "active",
778
+ expiresAt: agent.expiresAt,
779
+ createdAt: agent.createdAt,
780
+ updatedAt: agent.updatedAt
781
+ };
782
+ }
783
+ return { create, get, list, update, revoke, rotate, validateToken };
784
+ }
785
+ function toPermission(row) {
786
+ return {
787
+ resource: row.resource,
788
+ actions: row.actions,
789
+ constraints: row.constraints ?? void 0
790
+ };
791
+ }
792
+
793
+ export { createAgentModule };
794
+ //# sourceMappingURL=index.js.map
795
+ //# sourceMappingURL=index.js.map