@stratum-hq/control-plane 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/api-keys.test.d.ts +2 -0
- package/dist/__tests__/api-keys.test.d.ts.map +1 -0
- package/dist/__tests__/api-keys.test.js +240 -0
- package/dist/__tests__/api-keys.test.js.map +1 -0
- package/dist/__tests__/auth.test.d.ts +2 -0
- package/dist/__tests__/auth.test.d.ts.map +1 -0
- package/dist/__tests__/auth.test.js +227 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +201 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/tenants.test.d.ts +2 -0
- package/dist/__tests__/tenants.test.d.ts.map +1 -0
- package/dist/__tests__/tenants.test.js +214 -0
- package/dist/__tests__/tenants.test.js.map +1 -0
- package/dist/__tests__/test-helpers.d.ts +71 -0
- package/dist/__tests__/test-helpers.d.ts.map +1 -0
- package/dist/__tests__/test-helpers.js +172 -0
- package/dist/__tests__/test-helpers.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +114 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -0
- package/dist/db/connection.d.ts +4 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +30 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/migrate.d.ts +3 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +56 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/regional-pool-router.d.ts +9 -0
- package/dist/db/regional-pool-router.d.ts.map +1 -0
- package/dist/db/regional-pool-router.js +29 -0
- package/dist/db/regional-pool-router.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +21 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +70 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/authorize.d.ts +3 -0
- package/dist/middleware/authorize.d.ts.map +1 -0
- package/dist/middleware/authorize.js +54 -0
- package/dist/middleware/authorize.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +3 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +48 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/per-key-rate-limit.d.ts +3 -0
- package/dist/middleware/per-key-rate-limit.d.ts.map +1 -0
- package/dist/middleware/per-key-rate-limit.js +62 -0
- package/dist/middleware/per-key-rate-limit.js.map +1 -0
- package/dist/middleware/rate-limit-redis.d.ts +16 -0
- package/dist/middleware/rate-limit-redis.d.ts.map +1 -0
- package/dist/middleware/rate-limit-redis.js +118 -0
- package/dist/middleware/rate-limit-redis.js.map +1 -0
- package/dist/middleware/telemetry.d.ts +14 -0
- package/dist/middleware/telemetry.d.ts.map +1 -0
- package/dist/middleware/telemetry.js +81 -0
- package/dist/middleware/telemetry.js.map +1 -0
- package/dist/middleware/tenant-scope.d.ts +20 -0
- package/dist/middleware/tenant-scope.d.ts.map +1 -0
- package/dist/middleware/tenant-scope.js +60 -0
- package/dist/middleware/tenant-scope.js.map +1 -0
- package/dist/openapi.d.ts +3 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +44 -0
- package/dist/openapi.js.map +1 -0
- package/dist/redis.d.ts +16 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +71 -0
- package/dist/redis.js.map +1 -0
- package/dist/routes/api-keys.d.ts +4 -0
- package/dist/routes/api-keys.d.ts.map +1 -0
- package/dist/routes/api-keys.js +78 -0
- package/dist/routes/api-keys.js.map +1 -0
- package/dist/routes/audit-logs.d.ts +6 -0
- package/dist/routes/audit-logs.d.ts.map +1 -0
- package/dist/routes/audit-logs.js +43 -0
- package/dist/routes/audit-logs.js.map +1 -0
- package/dist/routes/config-diff.d.ts +4 -0
- package/dist/routes/config-diff.d.ts.map +1 -0
- package/dist/routes/config-diff.js +33 -0
- package/dist/routes/config-diff.js.map +1 -0
- package/dist/routes/config.d.ts +4 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +54 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/consent.d.ts +4 -0
- package/dist/routes/consent.d.ts.map +1 -0
- package/dist/routes/consent.js +45 -0
- package/dist/routes/consent.js.map +1 -0
- package/dist/routes/health.d.ts +9 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +32 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/maintenance.d.ts +4 -0
- package/dist/routes/maintenance.d.ts.map +1 -0
- package/dist/routes/maintenance.js +35 -0
- package/dist/routes/maintenance.js.map +1 -0
- package/dist/routes/permissions.d.ts +4 -0
- package/dist/routes/permissions.d.ts.map +1 -0
- package/dist/routes/permissions.js +36 -0
- package/dist/routes/permissions.js.map +1 -0
- package/dist/routes/regions.d.ts +4 -0
- package/dist/routes/regions.d.ts.map +1 -0
- package/dist/routes/regions.js +38 -0
- package/dist/routes/regions.js.map +1 -0
- package/dist/routes/roles.d.ts +4 -0
- package/dist/routes/roles.d.ts.map +1 -0
- package/dist/routes/roles.js +95 -0
- package/dist/routes/roles.js.map +1 -0
- package/dist/routes/tenants.d.ts +4 -0
- package/dist/routes/tenants.d.ts.map +1 -0
- package/dist/routes/tenants.js +112 -0
- package/dist/routes/tenants.js.map +1 -0
- package/dist/routes/webhooks.d.ts +4 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +116 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/services/isolation-service.d.ts +22 -0
- package/dist/services/isolation-service.d.ts.map +1 -0
- package/dist/services/isolation-service.js +94 -0
- package/dist/services/isolation-service.js.map +1 -0
- package/package.json +48 -0
package/dist/openapi.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerOpenApi = void 0;
|
|
7
|
+
const swagger_1 = __importDefault(require("@fastify/swagger"));
|
|
8
|
+
const swagger_ui_1 = __importDefault(require("@fastify/swagger-ui"));
|
|
9
|
+
async function registerOpenApi(app) {
|
|
10
|
+
await app.register(swagger_1.default, {
|
|
11
|
+
openapi: {
|
|
12
|
+
info: {
|
|
13
|
+
title: "Stratum Control Plane API",
|
|
14
|
+
version: "0.1.0",
|
|
15
|
+
description: "Universal Tenant Context Engine - Control Plane",
|
|
16
|
+
},
|
|
17
|
+
components: {
|
|
18
|
+
securitySchemes: {
|
|
19
|
+
apiKey: {
|
|
20
|
+
type: "apiKey",
|
|
21
|
+
in: "header",
|
|
22
|
+
name: "X-API-Key",
|
|
23
|
+
},
|
|
24
|
+
bearerAuth: {
|
|
25
|
+
type: "http",
|
|
26
|
+
scheme: "bearer",
|
|
27
|
+
bearerFormat: "JWT",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
security: [{ apiKey: [] }, { bearerAuth: [] }],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
await app.register(swagger_ui_1.default, {
|
|
35
|
+
routePrefix: "/api/docs",
|
|
36
|
+
uiConfig: {
|
|
37
|
+
docExpansion: "list",
|
|
38
|
+
deepLinking: true,
|
|
39
|
+
},
|
|
40
|
+
staticCSP: false,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
exports.registerOpenApi = registerOpenApi;
|
|
44
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":";;;;;;AACA,+DAAuC;AACvC,qEAA4C;AAErC,KAAK,UAAU,eAAe,CAAC,GAAoB;IACxD,MAAM,GAAG,CAAC,QAAQ,CAAC,iBAAc,EAAE;QACjC,OAAO,EAAE;YACP,IAAI,EAAE;gBACJ,KAAK,EAAE,2BAA2B;gBAClC,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,iDAAiD;aAC/D;YACD,UAAU,EAAE;gBACV,eAAe,EAAE;oBACf,MAAM,EAAE;wBACN,IAAI,EAAE,QAAQ;wBACd,EAAE,EAAE,QAAQ;wBACZ,IAAI,EAAE,WAAW;qBAClB;oBACD,UAAU,EAAE;wBACV,IAAI,EAAE,MAAM;wBACZ,MAAM,EAAE,QAAQ;wBAChB,YAAY,EAAE,KAAK;qBACpB;iBACF;aACF;YACD,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;SAC/C;KACF,CAAC,CAAC;IAEH,MAAM,GAAG,CAAC,QAAQ,CAAC,oBAAgB,EAAE;QACnC,WAAW,EAAE,WAAW;QACxB,QAAQ,EAAE;YACR,YAAY,EAAE,MAAM;YACpB,WAAW,EAAE,IAAI;SAClB;QACD,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC;AACL,CAAC;AAlCD,0CAkCC"}
|
package/dist/redis.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Redis from "ioredis";
|
|
2
|
+
/**
|
|
3
|
+
* Create a Redis client from the REDIS_URL env var.
|
|
4
|
+
* Returns null if REDIS_URL is not configured.
|
|
5
|
+
* Connection errors are logged but never crash the process (fail-open).
|
|
6
|
+
*/
|
|
7
|
+
export declare function createRedisClient(): Redis | null;
|
|
8
|
+
/**
|
|
9
|
+
* Get the current Redis client singleton (may be null).
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRedisClient(): Redis | null;
|
|
12
|
+
/**
|
|
13
|
+
* Health check: returns "connected", "disconnected", or "not_configured".
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkRedisHealth(): Promise<"connected" | "disconnected" | "not_configured">;
|
|
16
|
+
//# sourceMappingURL=redis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,SAAS,CAAC;AAK5B;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,KAAK,GAAG,IAAI,CAiChD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,KAAK,GAAG,IAAI,CAE7C;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,WAAW,GAAG,cAAc,GAAG,gBAAgB,CAAC,CAgBjG"}
|
package/dist/redis.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkRedisHealth = exports.getRedisClient = exports.createRedisClient = void 0;
|
|
7
|
+
const ioredis_1 = __importDefault(require("ioredis"));
|
|
8
|
+
const config_js_1 = require("./config.js");
|
|
9
|
+
let redisClient = null;
|
|
10
|
+
/**
|
|
11
|
+
* Create a Redis client from the REDIS_URL env var.
|
|
12
|
+
* Returns null if REDIS_URL is not configured.
|
|
13
|
+
* Connection errors are logged but never crash the process (fail-open).
|
|
14
|
+
*/
|
|
15
|
+
function createRedisClient() {
|
|
16
|
+
if (!config_js_1.config.redisUrl) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const client = new ioredis_1.default(config_js_1.config.redisUrl, {
|
|
20
|
+
maxRetriesPerRequest: 1,
|
|
21
|
+
retryStrategy(times) {
|
|
22
|
+
if (times > 3) {
|
|
23
|
+
// Stop retrying after 3 attempts — the app can run without Redis
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return Math.min(times * 200, 2000);
|
|
27
|
+
},
|
|
28
|
+
enableOfflineQueue: false,
|
|
29
|
+
lazyConnect: true,
|
|
30
|
+
});
|
|
31
|
+
client.on("error", (err) => {
|
|
32
|
+
console.warn("[stratum] Redis connection error (rate limiting will fall back to in-memory):", err.message);
|
|
33
|
+
});
|
|
34
|
+
client.on("connect", () => {
|
|
35
|
+
console.info("[stratum] Redis connected — using Redis-backed rate limiting.");
|
|
36
|
+
});
|
|
37
|
+
// Attempt to connect; failures are non-fatal
|
|
38
|
+
client.connect().catch((err) => {
|
|
39
|
+
console.warn("[stratum] Redis initial connection failed (continuing without Redis):", err.message);
|
|
40
|
+
});
|
|
41
|
+
redisClient = client;
|
|
42
|
+
return client;
|
|
43
|
+
}
|
|
44
|
+
exports.createRedisClient = createRedisClient;
|
|
45
|
+
/**
|
|
46
|
+
* Get the current Redis client singleton (may be null).
|
|
47
|
+
*/
|
|
48
|
+
function getRedisClient() {
|
|
49
|
+
return redisClient;
|
|
50
|
+
}
|
|
51
|
+
exports.getRedisClient = getRedisClient;
|
|
52
|
+
/**
|
|
53
|
+
* Health check: returns "connected", "disconnected", or "not_configured".
|
|
54
|
+
*/
|
|
55
|
+
async function checkRedisHealth() {
|
|
56
|
+
if (!redisClient) {
|
|
57
|
+
return "not_configured";
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await Promise.race([
|
|
61
|
+
redisClient.ping(),
|
|
62
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 1000)),
|
|
63
|
+
]);
|
|
64
|
+
return result === "PONG" ? "connected" : "disconnected";
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return "disconnected";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.checkRedisHealth = checkRedisHealth;
|
|
71
|
+
//# sourceMappingURL=redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../src/redis.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA4B;AAC5B,2CAAqC;AAErC,IAAI,WAAW,GAAiB,IAAI,CAAC;AAErC;;;;GAIG;AACH,SAAgB,iBAAiB;IAC/B,IAAI,CAAC,kBAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,iBAAK,CAAC,kBAAM,CAAC,QAAQ,EAAE;QACxC,oBAAoB,EAAE,CAAC;QACvB,aAAa,CAAC,KAAa;YACzB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACd,iEAAiE;gBACjE,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;QACD,kBAAkB,EAAE,KAAK;QACzB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,+EAA+E,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7G,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,6CAA6C;IAC7C,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7B,OAAO,CAAC,IAAI,CAAC,uEAAuE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACrG,CAAC,CAAC,CAAC;IAEH,WAAW,GAAG,MAAM,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC;AAjCD,8CAiCC;AAED;;GAEG;AACH,SAAgB,cAAc;IAC5B,OAAO,WAAW,CAAC;AACrB,CAAC;AAFD,wCAEC;AAED;;GAEG;AACI,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAChC,WAAW,CAAC,IAAI,EAAE;YAClB,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CACrD;SACF,CAAC,CAAC;QACH,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,CAAC;IACxB,CAAC;AACH,CAAC;AAhBD,4CAgBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../src/routes/api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAW1C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CAuExE"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createApiKeyRoutes = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const tenant_scope_js_1 = require("../middleware/tenant-scope.js");
|
|
6
|
+
const createApiKeySchema = zod_1.z.object({
|
|
7
|
+
tenant_id: zod_1.z.string().uuid(),
|
|
8
|
+
name: zod_1.z.string().optional(),
|
|
9
|
+
rate_limit_max: zod_1.z.number().int().min(1).max(100_000).optional(),
|
|
10
|
+
rate_limit_window: zod_1.z.string().regex(/^\d+\s+(second|minute|hour|day)s?$/i).optional(),
|
|
11
|
+
});
|
|
12
|
+
function createApiKeyRoutes(stratum) {
|
|
13
|
+
return async function apiKeyRoutes(app) {
|
|
14
|
+
// POST /api/v1/api-keys — Create a new API key (plaintext returned once)
|
|
15
|
+
app.post("/", { preHandler: (0, tenant_scope_js_1.createTenantScopeGuard)(stratum, tenant_scope_js_1.fromBodyTenantId) }, async (request, reply) => {
|
|
16
|
+
const parsed = createApiKeySchema.safeParse(request.body);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "Invalid request body" }, details: parsed.error.issues });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { tenant_id, name, rate_limit_max, rate_limit_window } = parsed.data;
|
|
22
|
+
const result = await stratum.createApiKey(tenant_id, {
|
|
23
|
+
name,
|
|
24
|
+
rateLimitMax: rate_limit_max,
|
|
25
|
+
rateLimitWindow: rate_limit_window,
|
|
26
|
+
});
|
|
27
|
+
reply.status(201).send(result);
|
|
28
|
+
});
|
|
29
|
+
// GET /api/v1/api-keys — List API keys (optional ?tenant_id=)
|
|
30
|
+
app.get("/", async (request, reply) => {
|
|
31
|
+
// Scoped keys can only list keys for their own tenant
|
|
32
|
+
const tenantId = request.apiKey?.tenant_id ?? request.query.tenant_id;
|
|
33
|
+
const keys = await stratum.listApiKeys(tenantId);
|
|
34
|
+
reply.status(200).send(keys);
|
|
35
|
+
});
|
|
36
|
+
// GET /api/v1/api-keys/dormant — List dormant API keys (must be before /:id)
|
|
37
|
+
app.get("/dormant", async (request, reply) => {
|
|
38
|
+
const raw = request.query.days ? parseInt(request.query.days, 10) : 90;
|
|
39
|
+
const days = Number.isNaN(raw) || raw < 1 ? 90 : Math.min(raw, 365);
|
|
40
|
+
const keys = await stratum.listDormantKeys(days);
|
|
41
|
+
reply.status(200).send(keys);
|
|
42
|
+
});
|
|
43
|
+
// POST /api/v1/api-keys/:id/rotate — Rotate an API key
|
|
44
|
+
app.post("/:id/rotate", async (request, reply) => {
|
|
45
|
+
// Verify caller has access to this key's tenant
|
|
46
|
+
if (request.apiKey?.tenant_id) {
|
|
47
|
+
const keys = await stratum.listApiKeys(request.apiKey.tenant_id);
|
|
48
|
+
if (!keys.some(k => k.id === request.params.id)) {
|
|
49
|
+
reply.status(404).send({ error: { code: "NOT_FOUND", message: "API key not found or already revoked" } });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const rotateSchema = zod_1.z.object({ name: zod_1.z.string().max(255).optional() });
|
|
54
|
+
const parsed = rotateSchema.parse(request.body ?? {});
|
|
55
|
+
const result = await stratum.rotateApiKey(request.params.id, parsed.name);
|
|
56
|
+
reply.status(201).send(result);
|
|
57
|
+
});
|
|
58
|
+
// DELETE /api/v1/api-keys/:id — Revoke an API key
|
|
59
|
+
app.delete("/:id", async (request, reply) => {
|
|
60
|
+
// Verify caller has access to this key's tenant
|
|
61
|
+
if (request.apiKey?.tenant_id) {
|
|
62
|
+
const keys = await stratum.listApiKeys(request.apiKey.tenant_id);
|
|
63
|
+
if (!keys.some(k => k.id === request.params.id)) {
|
|
64
|
+
reply.status(404).send({ error: { code: "NOT_FOUND", message: "API key not found or already revoked" } });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const revoked = await stratum.revokeApiKey(request.params.id);
|
|
69
|
+
if (!revoked) {
|
|
70
|
+
reply.status(404).send({ error: { code: "NOT_FOUND", message: "API key not found or already revoked" } });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
reply.status(204).send();
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
exports.createApiKeyRoutes = createApiKeyRoutes;
|
|
78
|
+
//# sourceMappingURL=api-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../src/routes/api-keys.ts"],"names":[],"mappings":";;;AAEA,6BAAwB;AACxB,mEAA4G;AAE5G,MAAM,kBAAkB,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IAC5B,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE;IAC/D,iBAAiB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC,QAAQ,EAAE;CACtF,CAAC,CAAC;AAEH,SAAgB,kBAAkB,CAAC,OAAgB;IACjD,OAAO,KAAK,UAAU,YAAY,CAAC,GAAoB;QACrD,yEAAyE;QACzE,GAAG,CAAC,IAAI,CACN,GAAG,EACH,EAAE,UAAU,EAAE,IAAA,wCAAsB,EAAC,OAAO,EAAE,kCAAgB,CAAC,EAAE,EACjE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/H,OAAO;YACT,CAAC;YACD,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YAC3E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE;gBACnD,IAAI;gBACJ,YAAY,EAAE,cAAc;gBAC5B,eAAe,EAAE,iBAAiB;aACnC,CAAC,CAAC;YACH,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CACF,CAAC;QAEF,8DAA8D;QAC9D,GAAG,CAAC,GAAG,CAA0C,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAC7E,sDAAsD;YACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;YACtE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YACjD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,6EAA6E;QAC7E,GAAG,CAAC,GAAG,CAAqC,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAC/E,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACpE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YACjD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,uDAAuD;QACvD,GAAG,CAAC,IAAI,CAA6B,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAC3E,gDAAgD;YAChD,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;oBAChD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sCAAsC,EAAE,EAAE,CAAC,CAAC;oBAC1G,OAAO;gBACT,CAAC;YACH,CAAC;YACD,MAAM,YAAY,GAAG,OAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1E,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,kDAAkD;QAClD,GAAG,CAAC,MAAM,CAA6B,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACtE,gDAAgD;YAChD,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;oBAChD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sCAAsC,EAAE,EAAE,CAAC,CAAC;oBAC1G,OAAO;gBACT,CAAC;YACH,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC9D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sCAAsC,EAAE,EAAE,CAAC,CAAC;gBAC1G,OAAO;YACT,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAxED,gDAwEC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest } from "fastify";
|
|
2
|
+
import type { AuditContext } from "@stratum-hq/core";
|
|
3
|
+
import { Stratum } from "@stratum-hq/lib";
|
|
4
|
+
export declare function buildAuditContext(request: FastifyRequest): AuditContext;
|
|
5
|
+
export declare function createAuditLogRoutes(stratum: Stratum): (app: FastifyInstance) => Promise<void>;
|
|
6
|
+
//# sourceMappingURL=audit-logs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logs.d.ts","sourceRoot":"","sources":["../../src/routes/audit-logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,CAOvE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CA2B1E"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAuditLogRoutes = exports.buildAuditContext = void 0;
|
|
4
|
+
const core_1 = require("@stratum-hq/core");
|
|
5
|
+
function buildAuditContext(request) {
|
|
6
|
+
return {
|
|
7
|
+
actor_id: request.apiKey?.id ?? "unknown",
|
|
8
|
+
actor_type: (request.authMethod || "api_key"),
|
|
9
|
+
source_ip: request.ip,
|
|
10
|
+
request_id: request.id,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
exports.buildAuditContext = buildAuditContext;
|
|
14
|
+
function createAuditLogRoutes(stratum) {
|
|
15
|
+
return async function auditLogRoutes(app) {
|
|
16
|
+
// GET /api/v1/audit-logs — List audit logs with filters
|
|
17
|
+
app.get("/", async (request, reply) => {
|
|
18
|
+
const query = core_1.AuditLogQuerySchema.parse(request.query);
|
|
19
|
+
// Scoped keys can only see logs for their own tenant
|
|
20
|
+
if (request.apiKey?.tenant_id) {
|
|
21
|
+
query.tenant_id = request.apiKey.tenant_id;
|
|
22
|
+
}
|
|
23
|
+
const entries = await stratum.queryAuditLogs(query);
|
|
24
|
+
reply.status(200).send(entries);
|
|
25
|
+
});
|
|
26
|
+
// GET /api/v1/audit-logs/:id — Get single audit entry
|
|
27
|
+
app.get("/:id", async (request, reply) => {
|
|
28
|
+
const entry = await stratum.getAuditEntry(request.params.id);
|
|
29
|
+
if (!entry) {
|
|
30
|
+
reply.status(404).send({ error: { code: "NOT_FOUND", message: "Audit entry not found" } });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Enforce tenant isolation for scoped keys
|
|
34
|
+
if (request.apiKey?.tenant_id && entry.tenant_id !== request.apiKey.tenant_id) {
|
|
35
|
+
reply.status(404).send({ error: { code: "NOT_FOUND", message: "Audit entry not found" } });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
reply.status(200).send(entry);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
exports.createAuditLogRoutes = createAuditLogRoutes;
|
|
43
|
+
//# sourceMappingURL=audit-logs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logs.js","sourceRoot":"","sources":["../../src/routes/audit-logs.ts"],"names":[],"mappings":";;;AACA,2CAAuD;AAIvD,SAAgB,iBAAiB,CAAC,OAAuB;IACvD,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,SAAS;QACzC,UAAU,EAAE,CAAC,OAAO,CAAC,UAAU,IAAI,SAAS,CAAsB;QAClE,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,UAAU,EAAE,OAAO,CAAC,EAAE;KACvB,CAAC;AACJ,CAAC;AAPD,8CAOC;AAED,SAAgB,oBAAoB,CAAC,OAAgB;IACnD,OAAO,KAAK,UAAU,cAAc,CAAC,GAAoB;QACvD,wDAAwD;QACxD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACpC,MAAM,KAAK,GAAG,0BAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACvD,qDAAqD;YACrD,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC9B,KAAK,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;YAC7C,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACpD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,sDAAsD;QACtD,GAAG,CAAC,GAAG,CAA6B,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACnE,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC;gBAC3F,OAAO;YACT,CAAC;YACD,2CAA2C;YAC3C,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBAC9E,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC;gBAC3F,OAAO;YACT,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AA5BD,oDA4BC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-diff.d.ts","sourceRoot":"","sources":["../../src/routes/config-diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CAgC5E"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createConfigDiffRoutes = void 0;
|
|
4
|
+
function createConfigDiffRoutes(stratum) {
|
|
5
|
+
return async function configDiffRoutes(app) {
|
|
6
|
+
// GET /api/v1/config/diff?tenant_a=UUID&tenant_b=UUID — Compare resolved config between two tenants
|
|
7
|
+
app.get("/diff", async (request, reply) => {
|
|
8
|
+
const { tenant_a, tenant_b } = request.query;
|
|
9
|
+
if (!tenant_a || !tenant_b) {
|
|
10
|
+
reply.status(400).send({
|
|
11
|
+
error: {
|
|
12
|
+
code: "VALIDATION_ERROR",
|
|
13
|
+
message: "Both tenant_a and tenant_b query parameters are required",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (tenant_a === tenant_b) {
|
|
19
|
+
reply.status(400).send({
|
|
20
|
+
error: {
|
|
21
|
+
code: "VALIDATION_ERROR",
|
|
22
|
+
message: "tenant_a and tenant_b must be different",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const diff = await stratum.diffConfig(tenant_a, tenant_b);
|
|
28
|
+
reply.status(200).send(diff);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
exports.createConfigDiffRoutes = createConfigDiffRoutes;
|
|
33
|
+
//# sourceMappingURL=config-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-diff.js","sourceRoot":"","sources":["../../src/routes/config-diff.ts"],"names":[],"mappings":";;;AAGA,SAAgB,sBAAsB,CAAC,OAAgB;IACrD,OAAO,KAAK,UAAU,gBAAgB,CAAC,GAAoB;QACzD,oGAAoG;QACpG,GAAG,CAAC,GAAG,CACL,OAAO,EACP,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;YAE7C,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC3B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACrB,KAAK,EAAE;wBACL,IAAI,EAAE,kBAAkB;wBACxB,OAAO,EAAE,0DAA0D;qBACpE;iBACF,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAC1B,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACrB,KAAK,EAAE;wBACL,IAAI,EAAE,kBAAkB;wBACxB,OAAO,EAAE,yCAAyC;qBACnD;iBACF,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC1D,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAjCD,wDAiCC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI1C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CAgDxE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createConfigRoutes = void 0;
|
|
4
|
+
const core_1 = require("@stratum-hq/core");
|
|
5
|
+
const audit_logs_js_1 = require("./audit-logs.js");
|
|
6
|
+
const tenant_scope_js_1 = require("../middleware/tenant-scope.js");
|
|
7
|
+
function createConfigRoutes(stratum) {
|
|
8
|
+
return async function configRoutes(app) {
|
|
9
|
+
// Tenant-scoped keys can only access config for their own tenant subtree
|
|
10
|
+
app.addHook("preHandler", (0, tenant_scope_js_1.createTenantScopeGuard)(stratum, tenant_scope_js_1.fromParamId));
|
|
11
|
+
// GET /api/v1/tenants/:id/config — Get resolved config
|
|
12
|
+
app.get("/", async (request, reply) => {
|
|
13
|
+
const resolved = await stratum.resolveConfig(request.params.id);
|
|
14
|
+
reply.status(200).send(resolved);
|
|
15
|
+
});
|
|
16
|
+
// PUT /api/v1/tenants/:id/config/:key — Set config value
|
|
17
|
+
app.put("/:key", async (request, reply) => {
|
|
18
|
+
const input = core_1.SetConfigInputSchema.parse(request.body);
|
|
19
|
+
const entry = await stratum.setConfig(request.params.id, request.params.key, input, (0, audit_logs_js_1.buildAuditContext)(request));
|
|
20
|
+
reply.status(200).send(entry);
|
|
21
|
+
});
|
|
22
|
+
// DELETE /api/v1/tenants/:id/config/:key — Delete config override
|
|
23
|
+
app.delete("/:key", async (request, reply) => {
|
|
24
|
+
await stratum.deleteConfig(request.params.id, request.params.key, (0, audit_logs_js_1.buildAuditContext)(request));
|
|
25
|
+
reply.status(204).send();
|
|
26
|
+
});
|
|
27
|
+
// PUT /api/v1/tenants/:id/config/batch — Set multiple config keys atomically
|
|
28
|
+
app.put("/batch", async (request, reply) => {
|
|
29
|
+
const body = request.body;
|
|
30
|
+
if (!Array.isArray(body?.entries) || body.entries.length === 0) {
|
|
31
|
+
reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "Body must contain a non-empty 'entries' array" } });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (body.entries.length > 200) {
|
|
35
|
+
reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "Batch limited to 200 entries" } });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const entries = body.entries.map((e) => {
|
|
39
|
+
const parsed = core_1.SetConfigInputSchema.parse(e);
|
|
40
|
+
const entry = e;
|
|
41
|
+
return { key: entry.key, value: parsed.value, locked: parsed.locked, sensitive: parsed.sensitive };
|
|
42
|
+
});
|
|
43
|
+
const batchResult = await stratum.batchSetConfig(request.params.id, entries, (0, audit_logs_js_1.buildAuditContext)(request));
|
|
44
|
+
reply.status(200).send(batchResult);
|
|
45
|
+
});
|
|
46
|
+
// GET /api/v1/tenants/:id/config/inheritance — Get full inheritance view
|
|
47
|
+
app.get("/inheritance", async (request, reply) => {
|
|
48
|
+
const inheritance = await stratum.getConfigWithInheritance(request.params.id);
|
|
49
|
+
reply.status(200).send(inheritance);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
exports.createConfigRoutes = createConfigRoutes;
|
|
54
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/routes/consent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI1C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CA2CzE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createConsentRoutes = void 0;
|
|
4
|
+
const core_1 = require("@stratum-hq/core");
|
|
5
|
+
const audit_logs_js_1 = require("./audit-logs.js");
|
|
6
|
+
const tenant_scope_js_1 = require("../middleware/tenant-scope.js");
|
|
7
|
+
function createConsentRoutes(stratum) {
|
|
8
|
+
return async function consentRoutes(app) {
|
|
9
|
+
// Tenant-scoped keys can only access consent for their own tenant subtree
|
|
10
|
+
app.addHook("preHandler", (0, tenant_scope_js_1.createTenantScopeGuard)(stratum, tenant_scope_js_1.fromParamTenantId));
|
|
11
|
+
// POST /api/v1/tenants/:tenantId/consent — Grant consent
|
|
12
|
+
app.post("/", async (request, reply) => {
|
|
13
|
+
const { tenantId } = request.params;
|
|
14
|
+
const input = core_1.GrantConsentInputSchema.parse(request.body);
|
|
15
|
+
const audit = (0, audit_logs_js_1.buildAuditContext)(request);
|
|
16
|
+
const record = await stratum.grantConsent(tenantId, input, audit);
|
|
17
|
+
reply.status(201).send(record);
|
|
18
|
+
});
|
|
19
|
+
// GET /api/v1/tenants/:tenantId/consent — List consent records
|
|
20
|
+
app.get("/", async (request, reply) => {
|
|
21
|
+
const { tenantId } = request.params;
|
|
22
|
+
const subjectId = request.query.subject_id;
|
|
23
|
+
const records = await stratum.listConsent(tenantId, subjectId);
|
|
24
|
+
reply.status(200).send(records);
|
|
25
|
+
});
|
|
26
|
+
// DELETE /api/v1/tenants/:tenantId/consent/:purpose — Revoke consent
|
|
27
|
+
app.delete("/:purpose", async (request, reply) => {
|
|
28
|
+
const { tenantId, purpose } = request.params;
|
|
29
|
+
const subjectId = request.query.subject_id;
|
|
30
|
+
if (!subjectId) {
|
|
31
|
+
reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "subject_id query parameter is required" } });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const audit = (0, audit_logs_js_1.buildAuditContext)(request);
|
|
35
|
+
const revoked = await stratum.revokeConsent(tenantId, subjectId, purpose, audit);
|
|
36
|
+
if (!revoked) {
|
|
37
|
+
reply.status(404).send({ error: { code: "NOT_FOUND", message: "Consent record not found" } });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
reply.status(200).send({ success: true });
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.createConsentRoutes = createConsentRoutes;
|
|
45
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/routes/consent.ts"],"names":[],"mappings":";;;AACA,2CAA2D;AAE3D,mDAAoD;AACpD,mEAA0F;AAE1F,SAAgB,mBAAmB,CAAC,OAAgB;IAClD,OAAO,KAAK,UAAU,aAAa,CAAC,GAAoB;QACtD,0EAA0E;QAC1E,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,IAAA,wCAAsB,EAAC,OAAO,EAAE,mCAAiB,CAAC,CAAC,CAAC;QAC9E,yDAAyD;QACzD,GAAG,CAAC,IAAI,CAAmC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACvE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;YACpC,MAAM,KAAK,GAAG,8BAAuB,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,IAAA,iCAAiB,EAAC,OAAO,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAClE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,+DAA+D;QAC/D,GAAG,CAAC,GAAG,CACL,GAAG,EACH,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;YACpC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;YAC3C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC/D,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC,CACF,CAAC;QAEF,qEAAqE;QACrE,GAAG,CAAC,MAAM,CACR,WAAW,EACX,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;YAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;YAC3C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,wCAAwC,EAAE,EAAE,CAAC,CAAC;gBACnH,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,IAAA,iCAAiB,EAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;YACjF,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,0BAA0B,EAAE,EAAE,CAAC,CAAC;gBAC9F,OAAO;YACT,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AA5CD,kDA4CC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FastifyInstance } from "fastify";
|
|
2
|
+
type RedisHealthChecker = () => Promise<"connected" | "disconnected" | "not_configured">;
|
|
3
|
+
/**
|
|
4
|
+
* Health routes factory. Accepts an optional Redis health checker so the
|
|
5
|
+
* health endpoint can report Redis status without a hard dependency on it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function healthRoutes(checkRedisHealth: RedisHealthChecker): (app: FastifyInstance) => Promise<void>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG1C,KAAK,kBAAkB,GAAG,MAAM,OAAO,CAAC,WAAW,GAAG,cAAc,GAAG,gBAAgB,CAAC,CAAC;AAEzF;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,gBAAgB,EAAE,kBAAkB,SAEU,eAAe,KAAG,QAAQ,IAAI,CAAC,CAsB9E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.healthRoutes = void 0;
|
|
4
|
+
const connection_js_1 = require("../db/connection.js");
|
|
5
|
+
/**
|
|
6
|
+
* Health routes factory. Accepts an optional Redis health checker so the
|
|
7
|
+
* health endpoint can report Redis status without a hard dependency on it.
|
|
8
|
+
*/
|
|
9
|
+
function healthRoutes(checkRedisHealth) {
|
|
10
|
+
return async function healthRoutesPlugin(app) {
|
|
11
|
+
app.get("/api/v1/health", async (_request, reply) => {
|
|
12
|
+
let dbStatus = "disconnected";
|
|
13
|
+
try {
|
|
14
|
+
const pool = (0, connection_js_1.getPool)();
|
|
15
|
+
await pool.query("SELECT 1");
|
|
16
|
+
dbStatus = "connected";
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
dbStatus = "disconnected";
|
|
20
|
+
}
|
|
21
|
+
const redisStatus = await checkRedisHealth();
|
|
22
|
+
reply.status(200).send({
|
|
23
|
+
status: "ok",
|
|
24
|
+
timestamp: new Date().toISOString(),
|
|
25
|
+
db: dbStatus,
|
|
26
|
+
redis: redisStatus,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
exports.healthRoutes = healthRoutes;
|
|
32
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":";;;AACA,uDAA8C;AAI9C;;;GAGG;AACH,SAAgB,YAAY,CAC1B,gBAAoC;IAEpC,OAAO,KAAK,UAAU,kBAAkB,CAAC,GAAoB;QAC3D,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YAClD,IAAI,QAAQ,GAAiC,cAAc,CAAC;YAE5D,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,IAAA,uBAAO,GAAE,CAAC;gBACvB,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC7B,QAAQ,GAAG,WAAW,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,GAAG,cAAc,CAAC;YAC5B,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,gBAAgB,EAAE,CAAC;YAE7C,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACrB,MAAM,EAAE,IAAI;gBACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,EAAE,EAAE,QAAQ;gBACZ,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAzBD,oCAyBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/routes/maintenance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG1C,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CA4B7E"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMaintenanceRoutes = void 0;
|
|
4
|
+
const audit_logs_js_1 = require("./audit-logs.js");
|
|
5
|
+
function createMaintenanceRoutes(stratum) {
|
|
6
|
+
return async function maintenanceRoutes(app) {
|
|
7
|
+
// POST /api/v1/maintenance/purge-expired — Purge expired data
|
|
8
|
+
app.post("/purge-expired", async (request, reply) => {
|
|
9
|
+
const rawDays = request.query.retention_days
|
|
10
|
+
? parseInt(request.query.retention_days, 10)
|
|
11
|
+
: undefined;
|
|
12
|
+
const retentionDays = rawDays !== undefined && (Number.isNaN(rawDays) || rawDays < 1)
|
|
13
|
+
? undefined
|
|
14
|
+
: rawDays !== undefined ? Math.min(rawDays, 3650) : undefined;
|
|
15
|
+
const result = await stratum.purgeExpiredData(retentionDays);
|
|
16
|
+
reply.status(200).send(result);
|
|
17
|
+
});
|
|
18
|
+
// POST /api/v1/maintenance/rotate-encryption-key — Re-encrypt all sensitive data
|
|
19
|
+
app.post("/rotate-encryption-key", async (request, reply) => {
|
|
20
|
+
const body = request.body;
|
|
21
|
+
if (!body?.old_key || !body?.new_key) {
|
|
22
|
+
reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "Both 'old_key' and 'new_key' are required" } });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (body.old_key === body.new_key) {
|
|
26
|
+
reply.status(400).send({ error: { code: "VALIDATION_ERROR", message: "'new_key' must differ from 'old_key'" } });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const result = await stratum.rotateEncryptionKey(body.old_key, body.new_key, (0, audit_logs_js_1.buildAuditContext)(request));
|
|
30
|
+
reply.status(200).send(result);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
exports.createMaintenanceRoutes = createMaintenanceRoutes;
|
|
35
|
+
//# sourceMappingURL=maintenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maintenance.js","sourceRoot":"","sources":["../../src/routes/maintenance.ts"],"names":[],"mappings":";;;AAEA,mDAAoD;AAEpD,SAAgB,uBAAuB,CAAC,OAAgB;IACtD,OAAO,KAAK,UAAU,iBAAiB,CAAC,GAAoB;QAC1D,8DAA8D;QAC9D,GAAG,CAAC,IAAI,CAA+C,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAChG,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc;gBAC1C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC5C,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,aAAa,GAAG,OAAO,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;gBACnF,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAChE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;YAC7D,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,iFAAiF;QACjF,GAAG,CAAC,IAAI,CAAC,wBAAwB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;YAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAqD,CAAC;YAC3E,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;gBACrC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,2CAA2C,EAAE,EAAE,CAAC,CAAC;gBACtH,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,sCAAsC,EAAE,EAAE,CAAC,CAAC;gBACjH,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAA,iCAAiB,EAAC,OAAO,CAAC,CAAC,CAAC;YACzG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AA7BD,0DA6BC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/routes/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI1C,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,SACT,eAAe,KAAG,QAAQ,IAAI,CAAC,CAkC5E"}
|