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