@agentlensai/server 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloud/auth/api-key-middleware.d.ts +66 -0
- package/dist/cloud/auth/api-key-middleware.d.ts.map +1 -0
- package/dist/cloud/auth/api-key-middleware.js +147 -0
- package/dist/cloud/auth/api-key-middleware.js.map +1 -0
- package/dist/cloud/auth/api-keys.d.ts +90 -0
- package/dist/cloud/auth/api-keys.d.ts.map +1 -0
- package/dist/cloud/auth/api-keys.js +162 -0
- package/dist/cloud/auth/api-keys.js.map +1 -0
- package/dist/cloud/auth/audit-log.d.ts +66 -0
- package/dist/cloud/auth/audit-log.d.ts.map +1 -0
- package/dist/cloud/auth/audit-log.js +92 -0
- package/dist/cloud/auth/audit-log.js.map +1 -0
- package/dist/cloud/auth/auth-service.d.ts +77 -0
- package/dist/cloud/auth/auth-service.d.ts.map +1 -0
- package/dist/cloud/auth/auth-service.js +229 -0
- package/dist/cloud/auth/auth-service.js.map +1 -0
- package/dist/cloud/auth/brute-force.d.ts +36 -0
- package/dist/cloud/auth/brute-force.d.ts.map +1 -0
- package/dist/cloud/auth/brute-force.js +67 -0
- package/dist/cloud/auth/brute-force.js.map +1 -0
- package/dist/cloud/auth/index.d.ts +11 -0
- package/dist/cloud/auth/index.d.ts.map +1 -0
- package/dist/cloud/auth/index.js +11 -0
- package/dist/cloud/auth/index.js.map +1 -0
- package/dist/cloud/auth/jwt.d.ts +34 -0
- package/dist/cloud/auth/jwt.d.ts.map +1 -0
- package/dist/cloud/auth/jwt.js +68 -0
- package/dist/cloud/auth/jwt.js.map +1 -0
- package/dist/cloud/auth/oauth.d.ts +37 -0
- package/dist/cloud/auth/oauth.d.ts.map +1 -0
- package/dist/cloud/auth/oauth.js +120 -0
- package/dist/cloud/auth/oauth.js.map +1 -0
- package/dist/cloud/auth/passwords.d.ts +25 -0
- package/dist/cloud/auth/passwords.d.ts.map +1 -0
- package/dist/cloud/auth/passwords.js +50 -0
- package/dist/cloud/auth/passwords.js.map +1 -0
- package/dist/cloud/auth/rbac.d.ts +51 -0
- package/dist/cloud/auth/rbac.d.ts.map +1 -0
- package/dist/cloud/auth/rbac.js +89 -0
- package/dist/cloud/auth/rbac.js.map +1 -0
- package/dist/cloud/auth/tokens.d.ts +18 -0
- package/dist/cloud/auth/tokens.d.ts.map +1 -0
- package/dist/cloud/auth/tokens.js +29 -0
- package/dist/cloud/auth/tokens.js.map +1 -0
- package/dist/cloud/billing/billing-service.d.ts +44 -0
- package/dist/cloud/billing/billing-service.d.ts.map +1 -0
- package/dist/cloud/billing/billing-service.js +153 -0
- package/dist/cloud/billing/billing-service.js.map +1 -0
- package/dist/cloud/billing/index.d.ts +11 -0
- package/dist/cloud/billing/index.d.ts.map +1 -0
- package/dist/cloud/billing/index.js +11 -0
- package/dist/cloud/billing/index.js.map +1 -0
- package/dist/cloud/billing/invoice-service.d.ts +57 -0
- package/dist/cloud/billing/invoice-service.d.ts.map +1 -0
- package/dist/cloud/billing/invoice-service.js +123 -0
- package/dist/cloud/billing/invoice-service.js.map +1 -0
- package/dist/cloud/billing/plan-management.d.ts +46 -0
- package/dist/cloud/billing/plan-management.d.ts.map +1 -0
- package/dist/cloud/billing/plan-management.js +157 -0
- package/dist/cloud/billing/plan-management.js.map +1 -0
- package/dist/cloud/billing/quota-enforcement.d.ts +53 -0
- package/dist/cloud/billing/quota-enforcement.d.ts.map +1 -0
- package/dist/cloud/billing/quota-enforcement.js +143 -0
- package/dist/cloud/billing/quota-enforcement.js.map +1 -0
- package/dist/cloud/billing/stripe-client.d.ts +142 -0
- package/dist/cloud/billing/stripe-client.d.ts.map +1 -0
- package/dist/cloud/billing/stripe-client.js +169 -0
- package/dist/cloud/billing/stripe-client.js.map +1 -0
- package/dist/cloud/billing/trial-service.d.ts +47 -0
- package/dist/cloud/billing/trial-service.d.ts.map +1 -0
- package/dist/cloud/billing/trial-service.js +104 -0
- package/dist/cloud/billing/trial-service.js.map +1 -0
- package/dist/cloud/billing/usage-metering.d.ts +83 -0
- package/dist/cloud/billing/usage-metering.d.ts.map +1 -0
- package/dist/cloud/billing/usage-metering.js +174 -0
- package/dist/cloud/billing/usage-metering.js.map +1 -0
- package/dist/cloud/ingestion/backpressure.d.ts +107 -0
- package/dist/cloud/ingestion/backpressure.d.ts.map +1 -0
- package/dist/cloud/ingestion/backpressure.js +134 -0
- package/dist/cloud/ingestion/backpressure.js.map +1 -0
- package/dist/cloud/ingestion/batch-writer.d.ts +115 -0
- package/dist/cloud/ingestion/batch-writer.d.ts.map +1 -0
- package/dist/cloud/ingestion/batch-writer.js +319 -0
- package/dist/cloud/ingestion/batch-writer.js.map +1 -0
- package/dist/cloud/ingestion/dlq-manager.d.ts +116 -0
- package/dist/cloud/ingestion/dlq-manager.d.ts.map +1 -0
- package/dist/cloud/ingestion/dlq-manager.js +244 -0
- package/dist/cloud/ingestion/dlq-manager.js.map +1 -0
- package/dist/cloud/ingestion/event-queue.d.ts +105 -0
- package/dist/cloud/ingestion/event-queue.d.ts.map +1 -0
- package/dist/cloud/ingestion/event-queue.js +185 -0
- package/dist/cloud/ingestion/event-queue.js.map +1 -0
- package/dist/cloud/ingestion/gateway.d.ts +68 -0
- package/dist/cloud/ingestion/gateway.d.ts.map +1 -0
- package/dist/cloud/ingestion/gateway.js +198 -0
- package/dist/cloud/ingestion/gateway.js.map +1 -0
- package/dist/cloud/ingestion/index.d.ts +7 -0
- package/dist/cloud/ingestion/index.d.ts.map +1 -0
- package/dist/cloud/ingestion/index.js +7 -0
- package/dist/cloud/ingestion/index.js.map +1 -0
- package/dist/cloud/ingestion/rate-limiter.d.ts +73 -0
- package/dist/cloud/ingestion/rate-limiter.d.ts.map +1 -0
- package/dist/cloud/ingestion/rate-limiter.js +153 -0
- package/dist/cloud/ingestion/rate-limiter.js.map +1 -0
- package/dist/cloud/migrate.d.ts +45 -0
- package/dist/cloud/migrate.d.ts.map +1 -0
- package/dist/cloud/migrate.js +147 -0
- package/dist/cloud/migrate.js.map +1 -0
- package/dist/cloud/migration/export-import.d.ts +56 -0
- package/dist/cloud/migration/export-import.d.ts.map +1 -0
- package/dist/cloud/migration/export-import.js +289 -0
- package/dist/cloud/migration/export-import.js.map +1 -0
- package/dist/cloud/migration/index.d.ts +5 -0
- package/dist/cloud/migration/index.d.ts.map +1 -0
- package/dist/cloud/migration/index.js +5 -0
- package/dist/cloud/migration/index.js.map +1 -0
- package/dist/cloud/org-service.d.ts +68 -0
- package/dist/cloud/org-service.d.ts.map +1 -0
- package/dist/cloud/org-service.js +169 -0
- package/dist/cloud/org-service.js.map +1 -0
- package/dist/cloud/partition-maintenance.d.ts +29 -0
- package/dist/cloud/partition-maintenance.d.ts.map +1 -0
- package/dist/cloud/partition-maintenance.js +96 -0
- package/dist/cloud/partition-maintenance.js.map +1 -0
- package/dist/cloud/retention/index.d.ts +7 -0
- package/dist/cloud/retention/index.d.ts.map +1 -0
- package/dist/cloud/retention/index.js +7 -0
- package/dist/cloud/retention/index.js.map +1 -0
- package/dist/cloud/retention/partition-management.d.ts +61 -0
- package/dist/cloud/retention/partition-management.d.ts.map +1 -0
- package/dist/cloud/retention/partition-management.js +167 -0
- package/dist/cloud/retention/partition-management.js.map +1 -0
- package/dist/cloud/retention/retention-job.d.ts +70 -0
- package/dist/cloud/retention/retention-job.d.ts.map +1 -0
- package/dist/cloud/retention/retention-job.js +160 -0
- package/dist/cloud/retention/retention-job.js.map +1 -0
- package/dist/cloud/retention/retention-policy.d.ts +27 -0
- package/dist/cloud/retention/retention-policy.d.ts.map +1 -0
- package/dist/cloud/retention/retention-policy.js +36 -0
- package/dist/cloud/retention/retention-policy.js.map +1 -0
- package/dist/cloud/routes/api-key-routes.d.ts +38 -0
- package/dist/cloud/routes/api-key-routes.d.ts.map +1 -0
- package/dist/cloud/routes/api-key-routes.js +84 -0
- package/dist/cloud/routes/api-key-routes.js.map +1 -0
- package/dist/cloud/routes/audit-routes.d.ts +36 -0
- package/dist/cloud/routes/audit-routes.d.ts.map +1 -0
- package/dist/cloud/routes/audit-routes.js +47 -0
- package/dist/cloud/routes/audit-routes.js.map +1 -0
- package/dist/cloud/routes/billing-routes.d.ts +51 -0
- package/dist/cloud/routes/billing-routes.d.ts.map +1 -0
- package/dist/cloud/routes/billing-routes.js +114 -0
- package/dist/cloud/routes/billing-routes.js.map +1 -0
- package/dist/cloud/routes/onboarding-routes.d.ts +34 -0
- package/dist/cloud/routes/onboarding-routes.d.ts.map +1 -0
- package/dist/cloud/routes/onboarding-routes.js +58 -0
- package/dist/cloud/routes/onboarding-routes.js.map +1 -0
- package/dist/cloud/routes/org-routes.d.ts +80 -0
- package/dist/cloud/routes/org-routes.d.ts.map +1 -0
- package/dist/cloud/routes/org-routes.js +153 -0
- package/dist/cloud/routes/org-routes.js.map +1 -0
- package/dist/cloud/routes/usage-routes.d.ts +18 -0
- package/dist/cloud/routes/usage-routes.d.ts.map +1 -0
- package/dist/cloud/routes/usage-routes.js +66 -0
- package/dist/cloud/routes/usage-routes.js.map +1 -0
- package/dist/cloud/storage/adapter.d.ts +102 -0
- package/dist/cloud/storage/adapter.d.ts.map +1 -0
- package/dist/cloud/storage/adapter.js +21 -0
- package/dist/cloud/storage/adapter.js.map +1 -0
- package/dist/cloud/storage/index.d.ts +8 -0
- package/dist/cloud/storage/index.d.ts.map +1 -0
- package/dist/cloud/storage/index.js +7 -0
- package/dist/cloud/storage/index.js.map +1 -0
- package/dist/cloud/storage/postgres-adapter.d.ts +34 -0
- package/dist/cloud/storage/postgres-adapter.d.ts.map +1 -0
- package/dist/cloud/storage/postgres-adapter.js +544 -0
- package/dist/cloud/storage/postgres-adapter.js.map +1 -0
- package/dist/cloud/storage/sqlite-adapter.d.ts +29 -0
- package/dist/cloud/storage/sqlite-adapter.d.ts.map +1 -0
- package/dist/cloud/storage/sqlite-adapter.js +176 -0
- package/dist/cloud/storage/sqlite-adapter.js.map +1 -0
- package/dist/cloud/tenant-pool.d.ts +49 -0
- package/dist/cloud/tenant-pool.d.ts.map +1 -0
- package/dist/cloud/tenant-pool.js +61 -0
- package/dist/cloud/tenant-pool.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Authentication Middleware (S-2.4)
|
|
3
|
+
*
|
|
4
|
+
* Extracts `Authorization: Bearer al_...`, looks up key by prefix,
|
|
5
|
+
* verifies hash, attaches org_id to request context.
|
|
6
|
+
* Cache-friendly: in-memory Map with 60s TTL (Redis-replaceable).
|
|
7
|
+
* `last_used_at` updated non-blocking. Full key never logged.
|
|
8
|
+
*/
|
|
9
|
+
import { ApiKeyService } from './api-keys.js';
|
|
10
|
+
export interface ApiKeyAuthContext {
|
|
11
|
+
orgId: string;
|
|
12
|
+
keyId: string;
|
|
13
|
+
scopes: string[];
|
|
14
|
+
rateLimitOverride: number | null;
|
|
15
|
+
environment: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ApiKeyAuthRequest {
|
|
18
|
+
headers: {
|
|
19
|
+
authorization?: string;
|
|
20
|
+
[k: string]: string | undefined;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface CacheEntry {
|
|
24
|
+
orgId: string;
|
|
25
|
+
keyId: string;
|
|
26
|
+
keyHash: string;
|
|
27
|
+
scopes: string[];
|
|
28
|
+
rateLimitOverride: number | null;
|
|
29
|
+
environment: string;
|
|
30
|
+
revoked: boolean;
|
|
31
|
+
cachedAt: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ApiKeyCache {
|
|
34
|
+
get(prefix: string): CacheEntry | undefined;
|
|
35
|
+
set(prefix: string, entry: CacheEntry): void;
|
|
36
|
+
delete(prefix: string): void;
|
|
37
|
+
}
|
|
38
|
+
export declare class InMemoryApiKeyCache implements ApiKeyCache {
|
|
39
|
+
private ttlMs;
|
|
40
|
+
private cache;
|
|
41
|
+
constructor(ttlMs?: number);
|
|
42
|
+
get(prefix: string): CacheEntry | undefined;
|
|
43
|
+
set(prefix: string, entry: CacheEntry): void;
|
|
44
|
+
delete(prefix: string): void;
|
|
45
|
+
/** For testing: get raw size */
|
|
46
|
+
get size(): number;
|
|
47
|
+
}
|
|
48
|
+
export declare class ApiKeyAuthMiddleware {
|
|
49
|
+
private keyService;
|
|
50
|
+
private cache;
|
|
51
|
+
constructor(keyService: ApiKeyService, cache?: ApiKeyCache);
|
|
52
|
+
/**
|
|
53
|
+
* Authenticate an API key from Authorization header.
|
|
54
|
+
* Returns auth context or throws.
|
|
55
|
+
*/
|
|
56
|
+
authenticate(authHeader: string | undefined): Promise<ApiKeyAuthContext>;
|
|
57
|
+
/**
|
|
58
|
+
* Invalidate cache for a key prefix (called on revocation).
|
|
59
|
+
*/
|
|
60
|
+
invalidateCache(prefix: string): void;
|
|
61
|
+
}
|
|
62
|
+
export declare class ApiKeyAuthError extends Error {
|
|
63
|
+
statusCode: number;
|
|
64
|
+
constructor(statusCode: number, message: string);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=api-key-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-middleware.d.ts","sourceRoot":"","sources":["../../../src/cloud/auth/api-key-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAM9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;CACtE;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC5C,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAMD,qBAAa,mBAAoB,YAAW,WAAW;IAGzC,OAAO,CAAC,KAAK;IAFzB,OAAO,CAAC,KAAK,CAAiC;gBAE1B,KAAK,GAAE,MAAe;IAE1C,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAU3C,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAI5C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI5B,gCAAgC;IAChC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAMD,qBAAa,oBAAoB;IAI7B,OAAO,CAAC,UAAU;IAHpB,OAAO,CAAC,KAAK,CAAc;gBAGjB,UAAU,EAAE,aAAa,EACjC,KAAK,CAAC,EAAE,WAAW;IAKrB;;;OAGG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA4F9E;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAGtC;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAE/B,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM,EACzB,OAAO,EAAE,MAAM;CAKlB"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Authentication Middleware (S-2.4)
|
|
3
|
+
*
|
|
4
|
+
* Extracts `Authorization: Bearer al_...`, looks up key by prefix,
|
|
5
|
+
* verifies hash, attaches org_id to request context.
|
|
6
|
+
* Cache-friendly: in-memory Map with 60s TTL (Redis-replaceable).
|
|
7
|
+
* `last_used_at` updated non-blocking. Full key never logged.
|
|
8
|
+
*/
|
|
9
|
+
import { ApiKeyService } from './api-keys.js';
|
|
10
|
+
// ═══════════════════════════════════════════
|
|
11
|
+
// In-Memory Cache (Redis-replaceable)
|
|
12
|
+
// ═══════════════════════════════════════════
|
|
13
|
+
export class InMemoryApiKeyCache {
|
|
14
|
+
ttlMs;
|
|
15
|
+
cache = new Map();
|
|
16
|
+
constructor(ttlMs = 60_000) {
|
|
17
|
+
this.ttlMs = ttlMs;
|
|
18
|
+
}
|
|
19
|
+
get(prefix) {
|
|
20
|
+
const entry = this.cache.get(prefix);
|
|
21
|
+
if (!entry)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (Date.now() - entry.cachedAt > this.ttlMs) {
|
|
24
|
+
this.cache.delete(prefix);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return entry;
|
|
28
|
+
}
|
|
29
|
+
set(prefix, entry) {
|
|
30
|
+
this.cache.set(prefix, entry);
|
|
31
|
+
}
|
|
32
|
+
delete(prefix) {
|
|
33
|
+
this.cache.delete(prefix);
|
|
34
|
+
}
|
|
35
|
+
/** For testing: get raw size */
|
|
36
|
+
get size() {
|
|
37
|
+
return this.cache.size;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ═══════════════════════════════════════════
|
|
41
|
+
// Middleware
|
|
42
|
+
// ═══════════════════════════════════════════
|
|
43
|
+
export class ApiKeyAuthMiddleware {
|
|
44
|
+
keyService;
|
|
45
|
+
cache;
|
|
46
|
+
constructor(keyService, cache) {
|
|
47
|
+
this.keyService = keyService;
|
|
48
|
+
this.cache = cache ?? new InMemoryApiKeyCache(60_000);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Authenticate an API key from Authorization header.
|
|
52
|
+
* Returns auth context or throws.
|
|
53
|
+
*/
|
|
54
|
+
async authenticate(authHeader) {
|
|
55
|
+
// 1. Extract bearer token
|
|
56
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
57
|
+
throw new ApiKeyAuthError(401, 'Missing or invalid Authorization header');
|
|
58
|
+
}
|
|
59
|
+
const fullKey = authHeader.slice(7); // strip "Bearer "
|
|
60
|
+
// 2. Validate key format
|
|
61
|
+
if (!fullKey.startsWith('al_live_') && !fullKey.startsWith('al_test_')) {
|
|
62
|
+
throw new ApiKeyAuthError(401, 'Invalid API key format');
|
|
63
|
+
}
|
|
64
|
+
const prefix = fullKey.slice(0, 16);
|
|
65
|
+
// 3. Check cache
|
|
66
|
+
const cached = this.cache.get(prefix);
|
|
67
|
+
if (cached) {
|
|
68
|
+
if (cached.revoked) {
|
|
69
|
+
throw new ApiKeyAuthError(401, 'Invalid or revoked API key');
|
|
70
|
+
}
|
|
71
|
+
// Verify hash even on cache hit (the cache stores hash, we verify full key)
|
|
72
|
+
const valid = await this.keyService.verifyKey(fullKey, cached.keyHash);
|
|
73
|
+
if (!valid) {
|
|
74
|
+
throw new ApiKeyAuthError(401, 'Invalid or revoked API key');
|
|
75
|
+
}
|
|
76
|
+
// Update last_used_at non-blocking
|
|
77
|
+
this.keyService.updateLastUsed(cached.keyId);
|
|
78
|
+
return {
|
|
79
|
+
orgId: cached.orgId,
|
|
80
|
+
keyId: cached.keyId,
|
|
81
|
+
scopes: cached.scopes,
|
|
82
|
+
rateLimitOverride: cached.rateLimitOverride,
|
|
83
|
+
environment: cached.environment,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// 4. Cache miss → DB lookup
|
|
87
|
+
const keyRecord = await this.keyService.findByPrefix(prefix);
|
|
88
|
+
if (!keyRecord) {
|
|
89
|
+
throw new ApiKeyAuthError(401, 'Invalid or revoked API key');
|
|
90
|
+
}
|
|
91
|
+
// 5. Check revocation
|
|
92
|
+
if (keyRecord.revoked_at) {
|
|
93
|
+
// Cache the revoked state
|
|
94
|
+
this.cache.set(prefix, {
|
|
95
|
+
orgId: keyRecord.org_id,
|
|
96
|
+
keyId: keyRecord.id,
|
|
97
|
+
keyHash: keyRecord.key_hash,
|
|
98
|
+
scopes: keyRecord.scopes,
|
|
99
|
+
rateLimitOverride: keyRecord.rate_limit_override,
|
|
100
|
+
environment: keyRecord.environment,
|
|
101
|
+
revoked: true,
|
|
102
|
+
cachedAt: Date.now(),
|
|
103
|
+
});
|
|
104
|
+
throw new ApiKeyAuthError(401, 'Invalid or revoked API key');
|
|
105
|
+
}
|
|
106
|
+
// 6. Verify hash
|
|
107
|
+
const valid = await this.keyService.verifyKey(fullKey, keyRecord.key_hash);
|
|
108
|
+
if (!valid) {
|
|
109
|
+
throw new ApiKeyAuthError(401, 'Invalid or revoked API key');
|
|
110
|
+
}
|
|
111
|
+
// 7. Cache the result
|
|
112
|
+
this.cache.set(prefix, {
|
|
113
|
+
orgId: keyRecord.org_id,
|
|
114
|
+
keyId: keyRecord.id,
|
|
115
|
+
keyHash: keyRecord.key_hash,
|
|
116
|
+
scopes: keyRecord.scopes,
|
|
117
|
+
rateLimitOverride: keyRecord.rate_limit_override,
|
|
118
|
+
environment: keyRecord.environment,
|
|
119
|
+
revoked: false,
|
|
120
|
+
cachedAt: Date.now(),
|
|
121
|
+
});
|
|
122
|
+
// 8. Update last_used_at non-blocking
|
|
123
|
+
this.keyService.updateLastUsed(keyRecord.id);
|
|
124
|
+
return {
|
|
125
|
+
orgId: keyRecord.org_id,
|
|
126
|
+
keyId: keyRecord.id,
|
|
127
|
+
scopes: keyRecord.scopes,
|
|
128
|
+
rateLimitOverride: keyRecord.rate_limit_override,
|
|
129
|
+
environment: keyRecord.environment,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Invalidate cache for a key prefix (called on revocation).
|
|
134
|
+
*/
|
|
135
|
+
invalidateCache(prefix) {
|
|
136
|
+
this.cache.delete(prefix);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export class ApiKeyAuthError extends Error {
|
|
140
|
+
statusCode;
|
|
141
|
+
constructor(statusCode, message) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.statusCode = statusCode;
|
|
144
|
+
this.name = 'ApiKeyAuthError';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=api-key-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key-middleware.js","sourceRoot":"","sources":["../../../src/cloud/auth/api-key-middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key CRUD Service (S-2.3)
|
|
3
|
+
*
|
|
4
|
+
* Create/list/revoke API keys. Keys follow `al_live_<random32>` / `al_test_<random32>` format.
|
|
5
|
+
* Store scrypt hash only — show full key once at creation.
|
|
6
|
+
* Tier limits: Free=2, Pro=10, Team=50.
|
|
7
|
+
*/
|
|
8
|
+
import type { MigrationClient } from '../migrate.js';
|
|
9
|
+
export type ApiKeyEnvironment = 'production' | 'staging' | 'development' | 'test';
|
|
10
|
+
export interface CreateApiKeyInput {
|
|
11
|
+
orgId: string;
|
|
12
|
+
name: string;
|
|
13
|
+
environment: ApiKeyEnvironment;
|
|
14
|
+
createdBy: string;
|
|
15
|
+
scopes?: string[];
|
|
16
|
+
rateLimitOverride?: number | null;
|
|
17
|
+
}
|
|
18
|
+
export interface ApiKeyRecord {
|
|
19
|
+
id: string;
|
|
20
|
+
org_id: string;
|
|
21
|
+
key_prefix: string;
|
|
22
|
+
name: string;
|
|
23
|
+
environment: ApiKeyEnvironment;
|
|
24
|
+
scopes: string[];
|
|
25
|
+
rate_limit_override: number | null;
|
|
26
|
+
created_by: string;
|
|
27
|
+
last_used_at: string | null;
|
|
28
|
+
revoked_at: string | null;
|
|
29
|
+
created_at: string;
|
|
30
|
+
}
|
|
31
|
+
export interface CreateApiKeyResult {
|
|
32
|
+
/** Full key — shown ONCE at creation */
|
|
33
|
+
fullKey: string;
|
|
34
|
+
record: ApiKeyRecord;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate a full API key: `al_live_<random32>` or `al_test_<random32>`.
|
|
38
|
+
* The "prefix" stored for lookup is the first 12 chars (e.g. `al_live_abcd`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateApiKey(environment: ApiKeyEnvironment): {
|
|
41
|
+
fullKey: string;
|
|
42
|
+
prefix: string;
|
|
43
|
+
};
|
|
44
|
+
export declare class ApiKeyService {
|
|
45
|
+
private db;
|
|
46
|
+
constructor(db: MigrationClient);
|
|
47
|
+
/**
|
|
48
|
+
* Create a new API key. Returns full key (show once) + record.
|
|
49
|
+
*/
|
|
50
|
+
create(input: CreateApiKeyInput): Promise<CreateApiKeyResult>;
|
|
51
|
+
/**
|
|
52
|
+
* List API keys for an org. Returns prefix + metadata only (no hash, no full key).
|
|
53
|
+
*/
|
|
54
|
+
list(orgId: string): Promise<ApiKeyRecord[]>;
|
|
55
|
+
/**
|
|
56
|
+
* List only active (non-revoked) keys for an org.
|
|
57
|
+
*/
|
|
58
|
+
listActive(orgId: string): Promise<ApiKeyRecord[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Revoke an API key. Sets revoked_at timestamp.
|
|
61
|
+
*/
|
|
62
|
+
revoke(orgId: string, keyId: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Look up a key by prefix. Used by auth middleware.
|
|
65
|
+
*/
|
|
66
|
+
findByPrefix(prefix: string): Promise<(ApiKeyRecord & {
|
|
67
|
+
key_hash: string;
|
|
68
|
+
}) | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Update last_used_at (non-blocking, fire-and-forget).
|
|
71
|
+
*/
|
|
72
|
+
updateLastUsed(keyId: string): void;
|
|
73
|
+
/**
|
|
74
|
+
* Verify a full API key against a stored hash.
|
|
75
|
+
*/
|
|
76
|
+
verifyKey(fullKey: string, storedHash: string): Promise<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Count active keys for an org.
|
|
79
|
+
*/
|
|
80
|
+
countActive(orgId: string): Promise<number>;
|
|
81
|
+
/**
|
|
82
|
+
* Enforce tier-based key limits.
|
|
83
|
+
*/
|
|
84
|
+
private enforceTierLimit;
|
|
85
|
+
}
|
|
86
|
+
export declare class ApiKeyError extends Error {
|
|
87
|
+
code: string;
|
|
88
|
+
constructor(code: string, message: string);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=api-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../../src/cloud/auth/api-keys.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAMrD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,iBAAiB,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;CACtB;AAqBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAQlG;AAMD,qBAAa,aAAa;IACZ,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,eAAe;IAEvC;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAgCnE;;OAEG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAYlD;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAYxD;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ5D;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IAYzF;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMnC;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItE;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQjD;;OAEG;YACW,gBAAgB;CAgB/B;AAED,qBAAa,WAAY,SAAQ,KAAK;IAE3B,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM;CAKlB"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key CRUD Service (S-2.3)
|
|
3
|
+
*
|
|
4
|
+
* Create/list/revoke API keys. Keys follow `al_live_<random32>` / `al_test_<random32>` format.
|
|
5
|
+
* Store scrypt hash only — show full key once at creation.
|
|
6
|
+
* Tier limits: Free=2, Pro=10, Team=50.
|
|
7
|
+
*/
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import { hashPassword, verifyPassword } from './passwords.js';
|
|
10
|
+
// ═══════════════════════════════════════════
|
|
11
|
+
// Constants
|
|
12
|
+
// ═══════════════════════════════════════════
|
|
13
|
+
const TIER_KEY_LIMITS = {
|
|
14
|
+
free: 2,
|
|
15
|
+
pro: 10,
|
|
16
|
+
team: 50,
|
|
17
|
+
enterprise: 200,
|
|
18
|
+
};
|
|
19
|
+
const KEY_PREFIX_LIVE = 'al_live_';
|
|
20
|
+
const KEY_PREFIX_TEST = 'al_test_';
|
|
21
|
+
const KEY_RANDOM_BYTES = 24; // 24 bytes → 32 base64url chars
|
|
22
|
+
// ═══════════════════════════════════════════
|
|
23
|
+
// Key Generation
|
|
24
|
+
// ═══════════════════════════════════════════
|
|
25
|
+
/**
|
|
26
|
+
* Generate a full API key: `al_live_<random32>` or `al_test_<random32>`.
|
|
27
|
+
* The "prefix" stored for lookup is the first 12 chars (e.g. `al_live_abcd`).
|
|
28
|
+
*/
|
|
29
|
+
export function generateApiKey(environment) {
|
|
30
|
+
const isTest = environment === 'test' || environment === 'development';
|
|
31
|
+
const base = isTest ? KEY_PREFIX_TEST : KEY_PREFIX_LIVE;
|
|
32
|
+
const random = randomBytes(KEY_RANDOM_BYTES).toString('base64url');
|
|
33
|
+
const fullKey = `${base}${random}`;
|
|
34
|
+
// Prefix = first 16 chars for uniqueness (e.g. `al_live_abcdefgh`)
|
|
35
|
+
const prefix = fullKey.slice(0, 16);
|
|
36
|
+
return { fullKey, prefix };
|
|
37
|
+
}
|
|
38
|
+
// ═══════════════════════════════════════════
|
|
39
|
+
// API Key Service
|
|
40
|
+
// ═══════════════════════════════════════════
|
|
41
|
+
export class ApiKeyService {
|
|
42
|
+
db;
|
|
43
|
+
constructor(db) {
|
|
44
|
+
this.db = db;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a new API key. Returns full key (show once) + record.
|
|
48
|
+
*/
|
|
49
|
+
async create(input) {
|
|
50
|
+
// Check tier limit
|
|
51
|
+
await this.enforceTierLimit(input.orgId);
|
|
52
|
+
const { fullKey, prefix } = generateApiKey(input.environment);
|
|
53
|
+
const keyHash = await hashPassword(fullKey);
|
|
54
|
+
const result = await this.db.query(`INSERT INTO api_keys (org_id, key_prefix, key_hash, name, environment, scopes, rate_limit_override, created_by)
|
|
55
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
56
|
+
RETURNING id, org_id, key_prefix, name, environment, scopes, rate_limit_override, created_by, last_used_at, revoked_at, created_at`, [
|
|
57
|
+
input.orgId,
|
|
58
|
+
prefix,
|
|
59
|
+
keyHash,
|
|
60
|
+
input.name,
|
|
61
|
+
input.environment,
|
|
62
|
+
JSON.stringify(input.scopes ?? ['ingest', 'query']),
|
|
63
|
+
input.rateLimitOverride ?? null,
|
|
64
|
+
input.createdBy,
|
|
65
|
+
]);
|
|
66
|
+
const record = result.rows[0];
|
|
67
|
+
// Parse scopes from JSONB string if needed
|
|
68
|
+
if (typeof record.scopes === 'string') {
|
|
69
|
+
record.scopes = JSON.parse(record.scopes);
|
|
70
|
+
}
|
|
71
|
+
return { fullKey, record };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* List API keys for an org. Returns prefix + metadata only (no hash, no full key).
|
|
75
|
+
*/
|
|
76
|
+
async list(orgId) {
|
|
77
|
+
const result = await this.db.query(`SELECT id, org_id, key_prefix, name, environment, scopes, rate_limit_override, created_by, last_used_at, revoked_at, created_at
|
|
78
|
+
FROM api_keys WHERE org_id = $1 ORDER BY created_at DESC`, [orgId]);
|
|
79
|
+
return result.rows.map((r) => {
|
|
80
|
+
if (typeof r.scopes === 'string')
|
|
81
|
+
r.scopes = JSON.parse(r.scopes);
|
|
82
|
+
return r;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* List only active (non-revoked) keys for an org.
|
|
87
|
+
*/
|
|
88
|
+
async listActive(orgId) {
|
|
89
|
+
const result = await this.db.query(`SELECT id, org_id, key_prefix, name, environment, scopes, rate_limit_override, created_by, last_used_at, revoked_at, created_at
|
|
90
|
+
FROM api_keys WHERE org_id = $1 AND revoked_at IS NULL ORDER BY created_at DESC`, [orgId]);
|
|
91
|
+
return result.rows.map((r) => {
|
|
92
|
+
if (typeof r.scopes === 'string')
|
|
93
|
+
r.scopes = JSON.parse(r.scopes);
|
|
94
|
+
return r;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Revoke an API key. Sets revoked_at timestamp.
|
|
99
|
+
*/
|
|
100
|
+
async revoke(orgId, keyId) {
|
|
101
|
+
const result = await this.db.query(`UPDATE api_keys SET revoked_at = now() WHERE id = $1 AND org_id = $2 AND revoked_at IS NULL`, [keyId, orgId]);
|
|
102
|
+
return result.rowCount > 0;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Look up a key by prefix. Used by auth middleware.
|
|
106
|
+
*/
|
|
107
|
+
async findByPrefix(prefix) {
|
|
108
|
+
const result = await this.db.query(`SELECT id, org_id, key_prefix, key_hash, name, environment, scopes, rate_limit_override, created_by, last_used_at, revoked_at, created_at
|
|
109
|
+
FROM api_keys WHERE key_prefix = $1`, [prefix]);
|
|
110
|
+
const row = result.rows[0];
|
|
111
|
+
if (!row)
|
|
112
|
+
return null;
|
|
113
|
+
if (typeof row.scopes === 'string')
|
|
114
|
+
row.scopes = JSON.parse(row.scopes);
|
|
115
|
+
return row;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Update last_used_at (non-blocking, fire-and-forget).
|
|
119
|
+
*/
|
|
120
|
+
updateLastUsed(keyId) {
|
|
121
|
+
this.db.query(`UPDATE api_keys SET last_used_at = now() WHERE id = $1`, [keyId]).catch(() => {
|
|
122
|
+
// Fire and forget — don't block request
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Verify a full API key against a stored hash.
|
|
127
|
+
*/
|
|
128
|
+
async verifyKey(fullKey, storedHash) {
|
|
129
|
+
return verifyPassword(fullKey, storedHash);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Count active keys for an org.
|
|
133
|
+
*/
|
|
134
|
+
async countActive(orgId) {
|
|
135
|
+
const result = await this.db.query(`SELECT COUNT(*)::int as count FROM api_keys WHERE org_id = $1 AND revoked_at IS NULL`, [orgId]);
|
|
136
|
+
return result.rows[0].count;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Enforce tier-based key limits.
|
|
140
|
+
*/
|
|
141
|
+
async enforceTierLimit(orgId) {
|
|
142
|
+
// Get org plan
|
|
143
|
+
const orgResult = await this.db.query(`SELECT plan FROM orgs WHERE id = $1`, [orgId]);
|
|
144
|
+
const org = orgResult.rows[0];
|
|
145
|
+
if (!org)
|
|
146
|
+
throw new ApiKeyError('org_not_found', 'Organization not found');
|
|
147
|
+
const limit = TIER_KEY_LIMITS[org.plan] ?? TIER_KEY_LIMITS.free;
|
|
148
|
+
const count = await this.countActive(orgId);
|
|
149
|
+
if (count >= limit) {
|
|
150
|
+
throw new ApiKeyError('key_limit_reached', `API key limit reached for ${org.plan} plan (${limit} keys). Upgrade to create more.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export class ApiKeyError extends Error {
|
|
155
|
+
code;
|
|
156
|
+
constructor(code, message) {
|
|
157
|
+
super(message);
|
|
158
|
+
this.code = code;
|
|
159
|
+
this.name = 'ApiKeyError';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=api-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../../src/cloud/auth/api-keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Log Service (S-2.6)
|
|
3
|
+
*
|
|
4
|
+
* Append-only, immutable audit log for security-relevant events.
|
|
5
|
+
* Supports: auth events, API key ops, member management, settings changes,
|
|
6
|
+
* data exports, billing events.
|
|
7
|
+
*/
|
|
8
|
+
import type { MigrationClient } from '../migrate.js';
|
|
9
|
+
export type AuditAction = 'auth.login' | 'auth.logout' | 'auth.login_failed' | 'auth.password_reset' | 'auth.email_verified' | 'api_key.created' | 'api_key.revoked' | 'member.invited' | 'member.removed' | 'member.role_changed' | 'settings.updated' | 'org.deleted' | 'org.ownership_transferred' | 'data.exported' | 'data.imported' | 'billing.plan_changed' | 'billing.payment_failed' | 'permission.denied';
|
|
10
|
+
export type ActorType = 'user' | 'api_key' | 'system';
|
|
11
|
+
export type AuditResult = 'success' | 'failure';
|
|
12
|
+
export interface AuditEntry {
|
|
13
|
+
id: string;
|
|
14
|
+
org_id: string;
|
|
15
|
+
actor_type: ActorType;
|
|
16
|
+
actor_id: string;
|
|
17
|
+
action: AuditAction;
|
|
18
|
+
resource_type: string;
|
|
19
|
+
resource_id: string | null;
|
|
20
|
+
details: Record<string, unknown> | null;
|
|
21
|
+
ip_address: string | null;
|
|
22
|
+
result: AuditResult;
|
|
23
|
+
created_at: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WriteAuditEntry {
|
|
26
|
+
org_id: string;
|
|
27
|
+
actor_type: ActorType;
|
|
28
|
+
actor_id: string;
|
|
29
|
+
action: AuditAction;
|
|
30
|
+
resource_type: string;
|
|
31
|
+
resource_id?: string | null;
|
|
32
|
+
details?: Record<string, unknown> | null;
|
|
33
|
+
ip_address?: string | null;
|
|
34
|
+
result: AuditResult;
|
|
35
|
+
}
|
|
36
|
+
export interface AuditQueryFilters {
|
|
37
|
+
org_id: string;
|
|
38
|
+
action?: AuditAction;
|
|
39
|
+
actor_id?: string;
|
|
40
|
+
resource_type?: string;
|
|
41
|
+
from?: Date;
|
|
42
|
+
to?: Date;
|
|
43
|
+
limit?: number;
|
|
44
|
+
offset?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare class AuditLogService {
|
|
47
|
+
private db;
|
|
48
|
+
constructor(db: MigrationClient);
|
|
49
|
+
/**
|
|
50
|
+
* Write an audit log entry. Append-only — no updates or deletes.
|
|
51
|
+
*/
|
|
52
|
+
write(entry: WriteAuditEntry): Promise<AuditEntry>;
|
|
53
|
+
/**
|
|
54
|
+
* Query audit log with filters. Results ordered by created_at DESC.
|
|
55
|
+
*/
|
|
56
|
+
query(filters: AuditQueryFilters): Promise<{
|
|
57
|
+
entries: AuditEntry[];
|
|
58
|
+
total: number;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* Export all audit log entries for an org within a time range.
|
|
62
|
+
* Returns JSON array (for download).
|
|
63
|
+
*/
|
|
64
|
+
export(org_id: string, from?: Date, to?: Date): Promise<AuditEntry[]>;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=audit-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../../../src/cloud/auth/audit-log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,WAAW,GAEnB,YAAY,GACZ,aAAa,GACb,mBAAmB,GACnB,qBAAqB,GACrB,qBAAqB,GAErB,iBAAiB,GACjB,iBAAiB,GAEjB,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GAErB,kBAAkB,GAClB,aAAa,GACb,2BAA2B,GAE3B,eAAe,GACf,eAAe,GAEf,sBAAsB,GACtB,wBAAwB,GAExB,mBAAmB,CAAC;AAExB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AACtD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,SAAS,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,SAAS,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,eAAe;IAEvC;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IAoBxD;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IA+C1F;;;OAGG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;CAqB5E"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Log Service (S-2.6)
|
|
3
|
+
*
|
|
4
|
+
* Append-only, immutable audit log for security-relevant events.
|
|
5
|
+
* Supports: auth events, API key ops, member management, settings changes,
|
|
6
|
+
* data exports, billing events.
|
|
7
|
+
*/
|
|
8
|
+
export class AuditLogService {
|
|
9
|
+
db;
|
|
10
|
+
constructor(db) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Write an audit log entry. Append-only — no updates or deletes.
|
|
15
|
+
*/
|
|
16
|
+
async write(entry) {
|
|
17
|
+
const result = await this.db.query(`INSERT INTO audit_log (org_id, actor_type, actor_id, action, resource_type, resource_id, details, ip_address, result)
|
|
18
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::inet, $9)
|
|
19
|
+
RETURNING *`, [
|
|
20
|
+
entry.org_id,
|
|
21
|
+
entry.actor_type,
|
|
22
|
+
entry.actor_id,
|
|
23
|
+
entry.action,
|
|
24
|
+
entry.resource_type,
|
|
25
|
+
entry.resource_id ?? null,
|
|
26
|
+
entry.details ? JSON.stringify(entry.details) : null,
|
|
27
|
+
entry.ip_address ?? null,
|
|
28
|
+
entry.result,
|
|
29
|
+
]);
|
|
30
|
+
return result.rows[0];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Query audit log with filters. Results ordered by created_at DESC.
|
|
34
|
+
*/
|
|
35
|
+
async query(filters) {
|
|
36
|
+
const conditions = ['org_id = $1'];
|
|
37
|
+
const params = [filters.org_id];
|
|
38
|
+
let paramIdx = 2;
|
|
39
|
+
if (filters.action) {
|
|
40
|
+
conditions.push(`action = $${paramIdx++}`);
|
|
41
|
+
params.push(filters.action);
|
|
42
|
+
}
|
|
43
|
+
if (filters.actor_id) {
|
|
44
|
+
conditions.push(`actor_id = $${paramIdx++}`);
|
|
45
|
+
params.push(filters.actor_id);
|
|
46
|
+
}
|
|
47
|
+
if (filters.resource_type) {
|
|
48
|
+
conditions.push(`resource_type = $${paramIdx++}`);
|
|
49
|
+
params.push(filters.resource_type);
|
|
50
|
+
}
|
|
51
|
+
if (filters.from) {
|
|
52
|
+
conditions.push(`created_at >= $${paramIdx++}`);
|
|
53
|
+
params.push(filters.from.toISOString());
|
|
54
|
+
}
|
|
55
|
+
if (filters.to) {
|
|
56
|
+
conditions.push(`created_at <= $${paramIdx++}`);
|
|
57
|
+
params.push(filters.to.toISOString());
|
|
58
|
+
}
|
|
59
|
+
const where = conditions.join(' AND ');
|
|
60
|
+
const limit = filters.limit ?? 50;
|
|
61
|
+
const offset = filters.offset ?? 0;
|
|
62
|
+
const [dataResult, countResult] = await Promise.all([
|
|
63
|
+
this.db.query(`SELECT * FROM audit_log WHERE ${where} ORDER BY created_at DESC LIMIT $${paramIdx++} OFFSET $${paramIdx++}`, [...params, limit, offset]),
|
|
64
|
+
this.db.query(`SELECT COUNT(*)::int as total FROM audit_log WHERE ${where}`, params),
|
|
65
|
+
]);
|
|
66
|
+
return {
|
|
67
|
+
entries: dataResult.rows,
|
|
68
|
+
total: countResult.rows[0].total,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Export all audit log entries for an org within a time range.
|
|
73
|
+
* Returns JSON array (for download).
|
|
74
|
+
*/
|
|
75
|
+
async export(org_id, from, to) {
|
|
76
|
+
const conditions = ['org_id = $1'];
|
|
77
|
+
const params = [org_id];
|
|
78
|
+
let paramIdx = 2;
|
|
79
|
+
if (from) {
|
|
80
|
+
conditions.push(`created_at >= $${paramIdx++}`);
|
|
81
|
+
params.push(from.toISOString());
|
|
82
|
+
}
|
|
83
|
+
if (to) {
|
|
84
|
+
conditions.push(`created_at <= $${paramIdx++}`);
|
|
85
|
+
params.push(to.toISOString());
|
|
86
|
+
}
|
|
87
|
+
const where = conditions.join(' AND ');
|
|
88
|
+
const result = await this.db.query(`SELECT * FROM audit_log WHERE ${where} ORDER BY created_at ASC`, params);
|
|
89
|
+
return result.rows;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=audit-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../../../src/cloud/auth/audit-log.ts"],"names":[],"mappings":"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"}
|