@valentine-efagene/qshelter-common 2.0.63 → 2.0.64

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.
@@ -1,3 +1,2 @@
1
- export * from './permission-cache';
2
1
  export * from './policy-evaluator';
3
2
  export * from './types';
@@ -1,3 +1,2 @@
1
- export * from './permission-cache';
2
1
  export * from './policy-evaluator';
3
2
  export * from './types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentine-efagene/qshelter-common",
3
- "version": "2.0.63",
3
+ "version": "2.0.64",
4
4
  "description": "Shared database schemas and utilities for QShelter services",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -0,0 +1,26 @@
1
+ -- CreateTable
2
+ CREATE TABLE `api_keys` (
3
+ `id` VARCHAR(191) NOT NULL,
4
+ `tenantId` VARCHAR(191) NOT NULL,
5
+ `name` VARCHAR(191) NOT NULL,
6
+ `description` TEXT NULL,
7
+ `provider` VARCHAR(191) NOT NULL,
8
+ `secretRef` VARCHAR(191) NOT NULL,
9
+ `scopes` JSON NOT NULL,
10
+ `enabled` BOOLEAN NOT NULL DEFAULT true,
11
+ `expiresAt` DATETIME(3) NULL,
12
+ `lastUsedAt` DATETIME(3) NULL,
13
+ `revokedAt` DATETIME(3) NULL,
14
+ `revokedBy` VARCHAR(191) NULL,
15
+ `createdBy` VARCHAR(191) NULL,
16
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
17
+ `updatedAt` DATETIME(3) NOT NULL,
18
+
19
+ INDEX `api_keys_tenantId_idx`(`tenantId`),
20
+ INDEX `api_keys_provider_idx`(`provider`),
21
+ INDEX `api_keys_enabled_idx`(`enabled`),
22
+ PRIMARY KEY (`id`)
23
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
24
+
25
+ -- AddForeignKey
26
+ ALTER TABLE `api_keys` ADD CONSTRAINT `api_keys_tenantId_fkey` FOREIGN KEY (`tenantId`) REFERENCES `tenants`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
@@ -321,10 +321,67 @@ model Tenant {
321
321
  documentTemplates DocumentTemplate[]
322
322
  offerLetters OfferLetter[]
323
323
 
324
+ // API keys for third-party integrations
325
+ apiKeys ApiKey[]
326
+
324
327
  @@index([subdomain])
325
328
  @@map("tenants")
326
329
  }
327
330
 
331
+ // =============================================================================
332
+ // API KEYS - Third-party integration credentials
333
+ // =============================================================================
334
+ // ApiKey enables partners/integrations to authenticate via token exchange.
335
+ //
336
+ // Flow:
337
+ // 1. Admin creates API key for a partner (POST /api-keys)
338
+ // 2. System generates secret, stores in Secrets Manager, returns id.secret ONCE
339
+ // 3. Partner calls token endpoint with id.secret (POST /api-keys/:id/token)
340
+ // 4. Token endpoint validates via Secrets Manager, returns short-lived JWT
341
+ // 5. Partner uses JWT for API requests; authorizer validates + resolves scopes
342
+ //
343
+ // Security:
344
+ // - Raw secret stored ONLY in AWS Secrets Manager (secretRef = ARN)
345
+ // - Secret returned only once at creation; admin must rotate if lost
346
+ // - Scopes define allowed operations (e.g., ["contract:read", "payment:read"])
347
+ // - Short-lived JWTs (5-15 min) minimize exposure on key compromise
348
+ // =============================================================================
349
+
350
+ model ApiKey {
351
+ id String @id @default(cuid())
352
+ tenantId String
353
+ tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
354
+
355
+ // Identification
356
+ name String // Human-readable name (e.g., "Paystack Integration")
357
+ description String? @db.Text // Optional description
358
+ provider String // Partner/vendor name (e.g., "paystack", "flutterwave")
359
+
360
+ // Secret management (NEVER store raw secret in DB)
361
+ secretRef String // AWS Secrets Manager ARN or name
362
+
363
+ // Permissions - scopes this API key is allowed to request
364
+ // Examples: ["contract:read", "payment:*", "property:read"]
365
+ scopes Json // JSON array of scope strings
366
+
367
+ // Lifecycle
368
+ enabled Boolean @default(true)
369
+ expiresAt DateTime? // Optional expiration date
370
+ lastUsedAt DateTime? // Updated on each token exchange
371
+ revokedAt DateTime? // Set when key is revoked
372
+ revokedBy String? // User ID who revoked
373
+
374
+ // Audit
375
+ createdBy String? // User ID who created
376
+ createdAt DateTime @default(now())
377
+ updatedAt DateTime @updatedAt
378
+
379
+ @@index([tenantId])
380
+ @@index([provider])
381
+ @@index([enabled])
382
+ @@map("api_keys")
383
+ }
384
+
328
385
  model RefreshToken {
329
386
  id String @id @default(cuid())
330
387
  // Use the JWT `jti` for indexed lookups and keep the raw JWT (optional)