@shipfox/api-secrets 2.0.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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/core/crypto.d.ts +25 -0
- package/dist/core/crypto.d.ts.map +1 -0
- package/dist/core/crypto.js +85 -0
- package/dist/core/crypto.js.map +1 -0
- package/dist/core/dek-manager.d.ts +16 -0
- package/dist/core/dek-manager.d.ts.map +1 -0
- package/dist/core/dek-manager.js +85 -0
- package/dist/core/dek-manager.js.map +1 -0
- package/dist/core/errors.d.ts +53 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +86 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fingerprint.d.ts +2 -0
- package/dist/core/fingerprint.d.ts.map +1 -0
- package/dist/core/fingerprint.js +9 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/index.d.ts +30 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +97 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/key-provider.d.ts +17 -0
- package/dist/core/key-provider.d.ts.map +1 -0
- package/dist/core/key-provider.js +45 -0
- package/dist/core/key-provider.js.map +1 -0
- package/dist/core/local-secret-store.d.ts +19 -0
- package/dist/core/local-secret-store.d.ts.map +1 -0
- package/dist/core/local-secret-store.js +40 -0
- package/dist/core/local-secret-store.js.map +1 -0
- package/dist/core/management.d.ts +37 -0
- package/dist/core/management.d.ts.map +1 -0
- package/dist/core/management.js +403 -0
- package/dist/core/management.js.map +1 -0
- package/dist/core/rotate-kek.d.ts +10 -0
- package/dist/core/rotate-kek.d.ts.map +1 -0
- package/dist/core/rotate-kek.js +94 -0
- package/dist/core/rotate-kek.js.map +1 -0
- package/dist/core/secret-store.d.ts +33 -0
- package/dist/core/secret-store.d.ts.map +1 -0
- package/dist/core/secret-store.js +209 -0
- package/dist/core/secret-store.js.map +1 -0
- package/dist/core/store-resolver.d.ts +18 -0
- package/dist/core/store-resolver.d.ts.map +1 -0
- package/dist/core/store-resolver.js +10 -0
- package/dist/core/store-resolver.js.map +1 -0
- package/dist/core/store-validation.d.ts +11 -0
- package/dist/core/store-validation.d.ts.map +1 -0
- package/dist/core/store-validation.js +30 -0
- package/dist/core/store-validation.js.map +1 -0
- package/dist/core/value-cipher.d.ts +10 -0
- package/dist/core/value-cipher.d.ts.map +1 -0
- package/dist/core/value-cipher.js +22 -0
- package/dist/core/value-cipher.js.map +1 -0
- package/dist/core/variable-store.d.ts +24 -0
- package/dist/core/variable-store.d.ts.map +1 -0
- package/dist/core/variable-store.js +194 -0
- package/dist/core/variable-store.js.map +1 -0
- package/dist/db/cap.d.ts +8 -0
- package/dist/db/cap.d.ts.map +1 -0
- package/dist/db/cap.js +39 -0
- package/dist/db/cap.js.map +1 -0
- package/dist/db/data-keys.d.ts +24 -0
- package/dist/db/data-keys.d.ts.map +1 -0
- package/dist/db/data-keys.js +61 -0
- package/dist/db/data-keys.js.map +1 -0
- package/dist/db/db.d.ts +1254 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +24 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +15 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/management.d.ts +50 -0
- package/dist/db/management.d.ts.map +1 -0
- package/dist/db/management.js +127 -0
- package/dist/db/management.js.map +1 -0
- package/dist/db/schema/common.d.ts +3 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +8 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/data-keys.d.ts +103 -0
- package/dist/db/schema/data-keys.d.ts.map +1 -0
- package/dist/db/schema/data-keys.js +24 -0
- package/dist/db/schema/data-keys.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/values.d.ts +193 -0
- package/dist/db/schema/values.d.ts.map +1 -0
- package/dist/db/schema/values.js +43 -0
- package/dist/db/schema/values.js.map +1 -0
- package/dist/db/schema/variables.d.ts +175 -0
- package/dist/db/schema/variables.d.ts.map +1 -0
- package/dist/db/schema/variables.js +41 -0
- package/dist/db/schema/variables.js.map +1 -0
- package/dist/db/scope.d.ts +20 -0
- package/dist/db/scope.d.ts.map +1 -0
- package/dist/db/scope.js +19 -0
- package/dist/db/scope.js.map +1 -0
- package/dist/db/values.d.ts +33 -0
- package/dist/db/values.d.ts.map +1 -0
- package/dist/db/values.js +119 -0
- package/dist/db/values.js.map +1 -0
- package/dist/db/variables.d.ts +32 -0
- package/dist/db/variables.d.ts.map +1 -0
- package/dist/db/variables.js +117 -0
- package/dist/db/variables.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +39 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +151 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/metrics/service.d.ts +2 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +21 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +2 -0
- package/dist/presentation/dto/index.d.ts.map +1 -0
- package/dist/presentation/dto/index.js +3 -0
- package/dist/presentation/dto/index.js.map +1 -0
- package/dist/presentation/dto/management.d.ts +7 -0
- package/dist/presentation/dto/management.d.ts.map +1 -0
- package/dist/presentation/dto/management.js +32 -0
- package/dist/presentation/dto/management.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-secret.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-secret.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-secret.js +34 -0
- package/dist/presentation/e2eRoutes/create-secret.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-variable.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-variable.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-variable.js +34 -0
- package/dist/presentation/e2eRoutes/create-variable.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +3 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +11 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/routes/auth.d.ts +16 -0
- package/dist/presentation/routes/auth.d.ts.map +1 -0
- package/dist/presentation/routes/auth.js +42 -0
- package/dist/presentation/routes/auth.js.map +1 -0
- package/dist/presentation/routes/batch-secrets.d.ts +2 -0
- package/dist/presentation/routes/batch-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/batch-secrets.js +44 -0
- package/dist/presentation/routes/batch-secrets.js.map +1 -0
- package/dist/presentation/routes/batch-variables.d.ts +2 -0
- package/dist/presentation/routes/batch-variables.d.ts.map +1 -0
- package/dist/presentation/routes/batch-variables.js +44 -0
- package/dist/presentation/routes/batch-variables.js.map +1 -0
- package/dist/presentation/routes/cursor.d.ts +3 -0
- package/dist/presentation/routes/cursor.d.ts.map +1 -0
- package/dist/presentation/routes/cursor.js +19 -0
- package/dist/presentation/routes/cursor.js.map +1 -0
- package/dist/presentation/routes/delete-secret.d.ts +2 -0
- package/dist/presentation/routes/delete-secret.d.ts.map +1 -0
- package/dist/presentation/routes/delete-secret.js +40 -0
- package/dist/presentation/routes/delete-secret.js.map +1 -0
- package/dist/presentation/routes/delete-variable.d.ts +2 -0
- package/dist/presentation/routes/delete-variable.d.ts.map +1 -0
- package/dist/presentation/routes/delete-variable.js +40 -0
- package/dist/presentation/routes/delete-variable.js.map +1 -0
- package/dist/presentation/routes/errors.d.ts +2 -0
- package/dist/presentation/routes/errors.d.ts.map +1 -0
- package/dist/presentation/routes/errors.js +73 -0
- package/dist/presentation/routes/errors.js.map +1 -0
- package/dist/presentation/routes/get-variable.d.ts +2 -0
- package/dist/presentation/routes/get-variable.d.ts.map +1 -0
- package/dist/presentation/routes/get-variable.js +42 -0
- package/dist/presentation/routes/get-variable.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +29 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-secrets.d.ts +2 -0
- package/dist/presentation/routes/list-secrets.d.ts.map +1 -0
- package/dist/presentation/routes/list-secrets.js +50 -0
- package/dist/presentation/routes/list-secrets.js.map +1 -0
- package/dist/presentation/routes/list-variables.d.ts +2 -0
- package/dist/presentation/routes/list-variables.d.ts.map +1 -0
- package/dist/presentation/routes/list-variables.js +50 -0
- package/dist/presentation/routes/list-variables.js.map +1 -0
- package/dist/presentation/routes/put-secret.d.ts +2 -0
- package/dist/presentation/routes/put-secret.d.ts.map +1 -0
- package/dist/presentation/routes/put-secret.js +52 -0
- package/dist/presentation/routes/put-secret.js.map +1 -0
- package/dist/presentation/routes/put-variable.d.ts +2 -0
- package/dist/presentation/routes/put-variable.d.ts.map +1 -0
- package/dist/presentation/routes/put-variable.js +52 -0
- package/dist/presentation/routes/put-variable.js.map +1 -0
- package/dist/presentation/routes/warnings.d.ts +5 -0
- package/dist/presentation/routes/warnings.d.ts.map +1 -0
- package/dist/presentation/routes/warnings.js +16 -0
- package/dist/presentation/routes/warnings.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_lowly_ogun.sql +59 -0
- package/drizzle/meta/0000_snapshot.json +547 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +77 -0
- package/src/config.test.ts +41 -0
- package/src/config.ts +41 -0
- package/src/core/crypto.test.ts +115 -0
- package/src/core/crypto.ts +105 -0
- package/src/core/dek-manager.test.ts +120 -0
- package/src/core/dek-manager.ts +98 -0
- package/src/core/errors.ts +100 -0
- package/src/core/fingerprint.test.ts +27 -0
- package/src/core/fingerprint.ts +14 -0
- package/src/core/index.ts +153 -0
- package/src/core/key-provider.test.ts +44 -0
- package/src/core/key-provider.ts +70 -0
- package/src/core/local-secret-store.ts +49 -0
- package/src/core/management.test.ts +37 -0
- package/src/core/management.ts +503 -0
- package/src/core/rotate-kek.test.ts +183 -0
- package/src/core/rotate-kek.ts +100 -0
- package/src/core/secret-store.test.ts +301 -0
- package/src/core/secret-store.ts +251 -0
- package/src/core/store-resolver.test.ts +20 -0
- package/src/core/store-resolver.ts +28 -0
- package/src/core/store-validation.ts +43 -0
- package/src/core/value-cipher.ts +26 -0
- package/src/core/variable-store.test.ts +106 -0
- package/src/core/variable-store.ts +232 -0
- package/src/db/cap.ts +58 -0
- package/src/db/data-keys.ts +99 -0
- package/src/db/db.ts +26 -0
- package/src/db/index.ts +45 -0
- package/src/db/management.ts +252 -0
- package/src/db/schema/common.ts +8 -0
- package/src/db/schema/data-keys.ts +31 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/values.ts +74 -0
- package/src/db/schema/variables.ts +71 -0
- package/src/db/scope.ts +42 -0
- package/src/db/values.ts +191 -0
- package/src/db/variables.ts +187 -0
- package/src/index.test.ts +11 -0
- package/src/index.ts +50 -0
- package/src/metrics/index.ts +19 -0
- package/src/metrics/instance.ts +219 -0
- package/src/metrics/service.ts +19 -0
- package/src/presentation/dto/index.ts +1 -0
- package/src/presentation/dto/management.ts +36 -0
- package/src/presentation/e2eRoutes/create-secret.ts +31 -0
- package/src/presentation/e2eRoutes/create-variable.ts +34 -0
- package/src/presentation/e2eRoutes/index.test.ts +94 -0
- package/src/presentation/e2eRoutes/index.ts +8 -0
- package/src/presentation/routes/auth.ts +50 -0
- package/src/presentation/routes/batch-secrets.ts +33 -0
- package/src/presentation/routes/batch-variables.ts +33 -0
- package/src/presentation/routes/cursor.ts +23 -0
- package/src/presentation/routes/delete-secret.ts +26 -0
- package/src/presentation/routes/delete-variable.ts +26 -0
- package/src/presentation/routes/errors.ts +63 -0
- package/src/presentation/routes/get-variable.ts +32 -0
- package/src/presentation/routes/index.ts +29 -0
- package/src/presentation/routes/list-secrets.ts +41 -0
- package/src/presentation/routes/list-variables.ts +41 -0
- package/src/presentation/routes/management.test.ts +452 -0
- package/src/presentation/routes/put-secret.ts +39 -0
- package/src/presentation/routes/put-variable.ts +39 -0
- package/src/presentation/routes/warnings.ts +23 -0
- package/test/env.ts +8 -0
- package/test/globalSetup.ts +18 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +7 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {closeApp, createApp} from '@shipfox/node-fastify';
|
|
2
|
+
import {eq} from 'drizzle-orm';
|
|
3
|
+
import {getSecret} from '#core/index.js';
|
|
4
|
+
import {db, secretValues, secretVariables} from '#db/index.js';
|
|
5
|
+
import {secretsE2eRoutes} from './index.js';
|
|
6
|
+
|
|
7
|
+
const ACTOR_ID = '11111111-1111-4111-8111-111111111111';
|
|
8
|
+
|
|
9
|
+
describe('secrets e2e routes', () => {
|
|
10
|
+
let app: Awaited<ReturnType<typeof createApp>>;
|
|
11
|
+
let workspaceId: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
await closeApp();
|
|
15
|
+
workspaceId = crypto.randomUUID();
|
|
16
|
+
app = await createApp({routes: [secretsE2eRoutes], swagger: false});
|
|
17
|
+
await app.ready();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
await db().delete(secretValues).where(eq(secretValues.workspaceId, workspaceId));
|
|
22
|
+
await db().delete(secretVariables).where(eq(secretVariables.workspaceId, workspaceId));
|
|
23
|
+
await closeApp();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('creates a secret setup row without exposing plaintext', async () => {
|
|
27
|
+
const value = 'seeded-secret-value';
|
|
28
|
+
|
|
29
|
+
const res = await app.inject({
|
|
30
|
+
method: 'POST',
|
|
31
|
+
url: '/secrets/secret',
|
|
32
|
+
payload: {
|
|
33
|
+
workspace_id: workspaceId,
|
|
34
|
+
actor_id: ACTOR_ID,
|
|
35
|
+
key: 'API_TOKEN',
|
|
36
|
+
value,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const stored = await getSecret({workspaceId, key: 'API_TOKEN'});
|
|
40
|
+
|
|
41
|
+
expect(res.statusCode).toBe(201);
|
|
42
|
+
expect(res.json()).toMatchObject({
|
|
43
|
+
key: 'API_TOKEN',
|
|
44
|
+
project_id: null,
|
|
45
|
+
last_edited_by: ACTOR_ID,
|
|
46
|
+
});
|
|
47
|
+
expect(stored).toBe(value);
|
|
48
|
+
expect(res.body).not.toContain(value);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('creates a variable setup row with its readable value', async () => {
|
|
52
|
+
const res = await app.inject({
|
|
53
|
+
method: 'POST',
|
|
54
|
+
url: '/secrets/variable',
|
|
55
|
+
payload: {
|
|
56
|
+
workspace_id: workspaceId,
|
|
57
|
+
actor_id: ACTOR_ID,
|
|
58
|
+
key: 'REGION',
|
|
59
|
+
value: 'eu-west-1',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(res.statusCode).toBe(201);
|
|
64
|
+
expect(res.json()).toMatchObject({
|
|
65
|
+
key: 'REGION',
|
|
66
|
+
project_id: null,
|
|
67
|
+
value: 'eu-west-1',
|
|
68
|
+
last_edited_by: ACTOR_ID,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('rejects invalid setup keys through route validation', async () => {
|
|
73
|
+
const res = await app.inject({
|
|
74
|
+
method: 'POST',
|
|
75
|
+
url: '/secrets/secret',
|
|
76
|
+
payload: {
|
|
77
|
+
workspace_id: workspaceId,
|
|
78
|
+
actor_id: ACTOR_ID,
|
|
79
|
+
key: 'bad-key',
|
|
80
|
+
value: 'seeded-secret-value',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(res.statusCode).toBe(400);
|
|
85
|
+
expect(res.json()).toMatchObject({code: 'validation-error'});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('registers e2e setup routes without user auth', () => {
|
|
89
|
+
expect(secretsE2eRoutes.prefix).toBe('/secrets');
|
|
90
|
+
for (const route of secretsE2eRoutes.routes) {
|
|
91
|
+
expect(route.auth).toBeUndefined();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type {RouteGroup} from '@shipfox/node-fastify';
|
|
2
|
+
import {createE2eSecretRoute} from './create-secret.js';
|
|
3
|
+
import {createE2eVariableRoute} from './create-variable.js';
|
|
4
|
+
|
|
5
|
+
export const secretsE2eRoutes: RouteGroup = {
|
|
6
|
+
prefix: '/secrets',
|
|
7
|
+
routes: [createE2eSecretRoute, createE2eVariableRoute],
|
|
8
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {requireWorkspaceAccess} from '@shipfox/api-auth-context';
|
|
2
|
+
import {requireProjectForWorkspace} from '@shipfox/api-projects';
|
|
3
|
+
import {ClientError} from '@shipfox/node-fastify';
|
|
4
|
+
import type {FastifyRequest} from 'fastify';
|
|
5
|
+
|
|
6
|
+
export interface ManagementAccess {
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function requireManagementRead(params: {
|
|
12
|
+
request: FastifyRequest;
|
|
13
|
+
workspaceId: string;
|
|
14
|
+
projectId?: string | undefined;
|
|
15
|
+
}): Promise<ManagementAccess> {
|
|
16
|
+
const membership = requireWorkspaceAccess({
|
|
17
|
+
request: params.request,
|
|
18
|
+
workspaceId: params.workspaceId,
|
|
19
|
+
});
|
|
20
|
+
if (params.projectId) {
|
|
21
|
+
await requireProjectForWorkspace({
|
|
22
|
+
workspaceId: params.workspaceId,
|
|
23
|
+
projectId: params.projectId,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {workspaceId: params.workspaceId, userId: membership.userId};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function requireManagementWrite(params: {
|
|
31
|
+
request: FastifyRequest;
|
|
32
|
+
workspaceId: string;
|
|
33
|
+
projectId?: string | undefined;
|
|
34
|
+
}): Promise<ManagementAccess> {
|
|
35
|
+
const membership = requireWorkspaceAccess({
|
|
36
|
+
request: params.request,
|
|
37
|
+
workspaceId: params.workspaceId,
|
|
38
|
+
});
|
|
39
|
+
if (membership.role !== 'admin') {
|
|
40
|
+
throw new ClientError('Workspace admin role required', 'forbidden', {status: 403});
|
|
41
|
+
}
|
|
42
|
+
if (params.projectId) {
|
|
43
|
+
await requireProjectForWorkspace({
|
|
44
|
+
workspaceId: params.workspaceId,
|
|
45
|
+
projectId: params.projectId,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {workspaceId: params.workspaceId, userId: membership.userId};
|
|
50
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {batchSecretsBodySchema, batchSecretsResponseSchema} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {defineRoute} from '@shipfox/node-fastify';
|
|
3
|
+
import {z} from 'zod';
|
|
4
|
+
import {setManagedSecrets} from '#core/index.js';
|
|
5
|
+
import {toSecretDto} from '#presentation/dto/index.js';
|
|
6
|
+
import {requireManagementWrite} from './auth.js';
|
|
7
|
+
import {translateManagementError} from './errors.js';
|
|
8
|
+
import {secretWarnings} from './warnings.js';
|
|
9
|
+
|
|
10
|
+
export const batchSecretsRoute = defineRoute({
|
|
11
|
+
method: 'POST',
|
|
12
|
+
path: '/secrets::batch',
|
|
13
|
+
description: 'Create or update multiple user-managed secrets in one scope.',
|
|
14
|
+
schema: {
|
|
15
|
+
params: z.object({workspaceId: z.string().uuid()}),
|
|
16
|
+
body: batchSecretsBodySchema,
|
|
17
|
+
response: {200: batchSecretsResponseSchema},
|
|
18
|
+
},
|
|
19
|
+
errorHandler: translateManagementError,
|
|
20
|
+
handler: async (request) => {
|
|
21
|
+
const {workspaceId} = request.params;
|
|
22
|
+
const {project_id: projectId, entries} = request.body;
|
|
23
|
+
const access = await requireManagementWrite({request, workspaceId, projectId});
|
|
24
|
+
const secrets = await setManagedSecrets({
|
|
25
|
+
workspaceId,
|
|
26
|
+
projectId,
|
|
27
|
+
actorId: access.userId,
|
|
28
|
+
entries,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {secrets: secrets.map(toSecretDto), warnings: secretWarnings(entries)};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {batchVariablesBodySchema, batchVariablesResponseSchema} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {defineRoute} from '@shipfox/node-fastify';
|
|
3
|
+
import {z} from 'zod';
|
|
4
|
+
import {setManagedVariables} from '#core/index.js';
|
|
5
|
+
import {toVariableDto} from '#presentation/dto/index.js';
|
|
6
|
+
import {requireManagementWrite} from './auth.js';
|
|
7
|
+
import {translateManagementError} from './errors.js';
|
|
8
|
+
import {variableWarnings} from './warnings.js';
|
|
9
|
+
|
|
10
|
+
export const batchVariablesRoute = defineRoute({
|
|
11
|
+
method: 'POST',
|
|
12
|
+
path: '/variables::batch',
|
|
13
|
+
description: 'Create or update multiple user-managed variables in one scope.',
|
|
14
|
+
schema: {
|
|
15
|
+
params: z.object({workspaceId: z.string().uuid()}),
|
|
16
|
+
body: batchVariablesBodySchema,
|
|
17
|
+
response: {200: batchVariablesResponseSchema},
|
|
18
|
+
},
|
|
19
|
+
errorHandler: translateManagementError,
|
|
20
|
+
handler: async (request) => {
|
|
21
|
+
const {workspaceId} = request.params;
|
|
22
|
+
const {project_id: projectId, entries} = request.body;
|
|
23
|
+
const access = await requireManagementWrite({request, workspaceId, projectId});
|
|
24
|
+
const variables = await setManagedVariables({
|
|
25
|
+
workspaceId,
|
|
26
|
+
projectId,
|
|
27
|
+
actorId: access.userId,
|
|
28
|
+
entries,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {variables: variables.map(toVariableDto), warnings: variableWarnings(entries)};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface ManagementCursor {
|
|
2
|
+
key: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function encodeManagementCursor(cursor: string): string {
|
|
6
|
+
return Buffer.from(JSON.stringify({key: cursor} satisfies ManagementCursor), 'utf8').toString(
|
|
7
|
+
'base64url',
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function decodeManagementCursor(cursor: string | undefined): string | undefined {
|
|
12
|
+
if (!cursor) return undefined;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const parsed: unknown = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
16
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined;
|
|
17
|
+
const {key} = parsed as Partial<ManagementCursor>;
|
|
18
|
+
if (typeof key !== 'string' || key.length === 0) return undefined;
|
|
19
|
+
return key;
|
|
20
|
+
} catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {secretKeySchema, secretScopeQuerySchema} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {defineRoute} from '@shipfox/node-fastify';
|
|
3
|
+
import {z} from 'zod';
|
|
4
|
+
import {deleteManagedSecret} from '#core/index.js';
|
|
5
|
+
import {requireManagementWrite} from './auth.js';
|
|
6
|
+
import {translateManagementError} from './errors.js';
|
|
7
|
+
|
|
8
|
+
export const deleteSecretRoute = defineRoute({
|
|
9
|
+
method: 'DELETE',
|
|
10
|
+
path: '/secrets/:key',
|
|
11
|
+
description: 'Delete a user-managed secret.',
|
|
12
|
+
schema: {
|
|
13
|
+
params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
|
|
14
|
+
querystring: secretScopeQuerySchema,
|
|
15
|
+
response: {204: z.void()},
|
|
16
|
+
},
|
|
17
|
+
errorHandler: translateManagementError,
|
|
18
|
+
handler: async (request, reply) => {
|
|
19
|
+
const {workspaceId, key} = request.params;
|
|
20
|
+
const {project_id: projectId} = request.query;
|
|
21
|
+
const access = await requireManagementWrite({request, workspaceId, projectId});
|
|
22
|
+
|
|
23
|
+
await deleteManagedSecret({workspaceId, projectId, key, actorId: access.userId});
|
|
24
|
+
return reply.status(204).send();
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {secretKeySchema, secretScopeQuerySchema} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {defineRoute} from '@shipfox/node-fastify';
|
|
3
|
+
import {z} from 'zod';
|
|
4
|
+
import {deleteManagedVariable} from '#core/index.js';
|
|
5
|
+
import {requireManagementWrite} from './auth.js';
|
|
6
|
+
import {translateManagementError} from './errors.js';
|
|
7
|
+
|
|
8
|
+
export const deleteVariableRoute = defineRoute({
|
|
9
|
+
method: 'DELETE',
|
|
10
|
+
path: '/variables/:key',
|
|
11
|
+
description: 'Delete a user-managed variable.',
|
|
12
|
+
schema: {
|
|
13
|
+
params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
|
|
14
|
+
querystring: secretScopeQuerySchema,
|
|
15
|
+
response: {204: z.void()},
|
|
16
|
+
},
|
|
17
|
+
errorHandler: translateManagementError,
|
|
18
|
+
handler: async (request, reply) => {
|
|
19
|
+
const {workspaceId, key} = request.params;
|
|
20
|
+
const {project_id: projectId} = request.query;
|
|
21
|
+
const access = await requireManagementWrite({request, workspaceId, projectId});
|
|
22
|
+
|
|
23
|
+
await deleteManagedVariable({workspaceId, projectId, key, actorId: access.userId});
|
|
24
|
+
return reply.status(204).send();
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {ProjectNotFoundError} from '@shipfox/api-projects';
|
|
2
|
+
import {ClientError} from '@shipfox/node-fastify';
|
|
3
|
+
import {
|
|
4
|
+
NamespaceValidationError,
|
|
5
|
+
SecretBatchDuplicateKeyError,
|
|
6
|
+
SecretBatchScopeMismatchError,
|
|
7
|
+
SecretKeyValidationError,
|
|
8
|
+
SecretNotFoundError,
|
|
9
|
+
SecretValueTooLargeError,
|
|
10
|
+
UnknownSecretStoreError,
|
|
11
|
+
VariableNotFoundError,
|
|
12
|
+
WorkspaceSecretCapExceededError,
|
|
13
|
+
} from '#core/errors.js';
|
|
14
|
+
|
|
15
|
+
export function translateManagementError(error: unknown): never {
|
|
16
|
+
if (error instanceof SecretKeyValidationError) {
|
|
17
|
+
throw new ClientError('Invalid secret key', 'invalid-key', {
|
|
18
|
+
status: 400,
|
|
19
|
+
details: {key: error.key},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (error instanceof NamespaceValidationError) {
|
|
23
|
+
throw new ClientError('Invalid namespace', 'invalid-namespace', {
|
|
24
|
+
status: 400,
|
|
25
|
+
details: {namespace: error.namespace},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (error instanceof SecretValueTooLargeError) {
|
|
29
|
+
throw new ClientError(error.message, 'value-too-large', {
|
|
30
|
+
status: 413,
|
|
31
|
+
details: {max_bytes: error.maxBytes},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (error instanceof WorkspaceSecretCapExceededError) {
|
|
35
|
+
throw new ClientError('Workspace secret cap exceeded', 'workspace-secret-cap-exceeded', {
|
|
36
|
+
status: 409,
|
|
37
|
+
details: {cap: error.cap},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (error instanceof SecretBatchScopeMismatchError) {
|
|
41
|
+
throw new ClientError(error.message, 'batch-scope-mismatch', {status: 400});
|
|
42
|
+
}
|
|
43
|
+
if (error instanceof SecretBatchDuplicateKeyError) {
|
|
44
|
+
throw new ClientError('Duplicate batch key', 'duplicate-key', {
|
|
45
|
+
status: 400,
|
|
46
|
+
details: {key: error.key},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (error instanceof SecretNotFoundError) {
|
|
50
|
+
throw new ClientError('Secret not found', 'secret-not-found', {status: 404});
|
|
51
|
+
}
|
|
52
|
+
if (error instanceof VariableNotFoundError) {
|
|
53
|
+
throw new ClientError('Variable not found', 'variable-not-found', {status: 404});
|
|
54
|
+
}
|
|
55
|
+
if (error instanceof UnknownSecretStoreError) {
|
|
56
|
+
throw new ClientError('Unknown secret store', 'unknown-secret-store', {status: 400});
|
|
57
|
+
}
|
|
58
|
+
if (error instanceof ProjectNotFoundError) {
|
|
59
|
+
throw new ClientError('Project not found', 'project-not-found', {status: 404});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getVariableResponseSchema,
|
|
3
|
+
secretKeySchema,
|
|
4
|
+
secretScopeQuerySchema,
|
|
5
|
+
} from '@shipfox/api-secrets-dto';
|
|
6
|
+
import {defineRoute} from '@shipfox/node-fastify';
|
|
7
|
+
import {z} from 'zod';
|
|
8
|
+
import {getManagedVariable} from '#core/index.js';
|
|
9
|
+
import {toVariableDto} from '#presentation/dto/index.js';
|
|
10
|
+
import {requireManagementRead} from './auth.js';
|
|
11
|
+
import {translateManagementError} from './errors.js';
|
|
12
|
+
|
|
13
|
+
export const getVariableRoute = defineRoute({
|
|
14
|
+
method: 'GET',
|
|
15
|
+
path: '/variables/:key',
|
|
16
|
+
description: 'Read a user-managed variable.',
|
|
17
|
+
schema: {
|
|
18
|
+
params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
|
|
19
|
+
querystring: secretScopeQuerySchema,
|
|
20
|
+
response: {200: getVariableResponseSchema},
|
|
21
|
+
},
|
|
22
|
+
errorHandler: translateManagementError,
|
|
23
|
+
handler: async (request) => {
|
|
24
|
+
const {workspaceId, key} = request.params;
|
|
25
|
+
const {project_id: projectId} = request.query;
|
|
26
|
+
|
|
27
|
+
await requireManagementRead({request, workspaceId, projectId});
|
|
28
|
+
const variable = await getManagedVariable({workspaceId, projectId, key});
|
|
29
|
+
|
|
30
|
+
return {variable: toVariableDto(variable)};
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {AUTH_USER} from '@shipfox/api-auth-context';
|
|
2
|
+
import type {RouteGroup} from '@shipfox/node-fastify';
|
|
3
|
+
import {batchSecretsRoute} from './batch-secrets.js';
|
|
4
|
+
import {batchVariablesRoute} from './batch-variables.js';
|
|
5
|
+
import {deleteSecretRoute} from './delete-secret.js';
|
|
6
|
+
import {deleteVariableRoute} from './delete-variable.js';
|
|
7
|
+
import {getVariableRoute} from './get-variable.js';
|
|
8
|
+
import {listSecretsRoute} from './list-secrets.js';
|
|
9
|
+
import {listVariablesRoute} from './list-variables.js';
|
|
10
|
+
import {putSecretRoute} from './put-secret.js';
|
|
11
|
+
import {putVariableRoute} from './put-variable.js';
|
|
12
|
+
|
|
13
|
+
export const secretsRoutes: RouteGroup[] = [
|
|
14
|
+
{
|
|
15
|
+
prefix: '/workspaces/:workspaceId',
|
|
16
|
+
auth: AUTH_USER,
|
|
17
|
+
routes: [
|
|
18
|
+
listSecretsRoute,
|
|
19
|
+
putSecretRoute,
|
|
20
|
+
batchSecretsRoute,
|
|
21
|
+
deleteSecretRoute,
|
|
22
|
+
listVariablesRoute,
|
|
23
|
+
getVariableRoute,
|
|
24
|
+
putVariableRoute,
|
|
25
|
+
batchVariablesRoute,
|
|
26
|
+
deleteVariableRoute,
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {listSecretsQuerySchema, listSecretsResponseSchema} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
3
|
+
import {z} from 'zod';
|
|
4
|
+
import {listManagedSecrets} from '#core/index.js';
|
|
5
|
+
import {toSecretDto} from '#presentation/dto/index.js';
|
|
6
|
+
import {requireManagementRead} from './auth.js';
|
|
7
|
+
import {decodeManagementCursor, encodeManagementCursor} from './cursor.js';
|
|
8
|
+
import {translateManagementError} from './errors.js';
|
|
9
|
+
|
|
10
|
+
export const listSecretsRoute = defineRoute({
|
|
11
|
+
method: 'GET',
|
|
12
|
+
path: '/secrets',
|
|
13
|
+
description: 'List user-managed secrets for a workspace or project scope.',
|
|
14
|
+
schema: {
|
|
15
|
+
params: z.object({workspaceId: z.string().uuid()}),
|
|
16
|
+
querystring: listSecretsQuerySchema,
|
|
17
|
+
response: {200: listSecretsResponseSchema},
|
|
18
|
+
},
|
|
19
|
+
errorHandler: translateManagementError,
|
|
20
|
+
handler: async (request) => {
|
|
21
|
+
const {workspaceId} = request.params;
|
|
22
|
+
const {project_id: projectId, limit, cursor} = request.query;
|
|
23
|
+
const decodedCursor = decodeManagementCursor(cursor);
|
|
24
|
+
if (cursor && !decodedCursor) {
|
|
25
|
+
throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await requireManagementRead({request, workspaceId, projectId});
|
|
29
|
+
const result = await listManagedSecrets({
|
|
30
|
+
workspaceId,
|
|
31
|
+
projectId,
|
|
32
|
+
limit,
|
|
33
|
+
cursor: decodedCursor ?? undefined,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
secrets: result.secrets.map(toSecretDto),
|
|
38
|
+
next_cursor: result.nextCursor ? encodeManagementCursor(result.nextCursor) : null,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {listVariablesQuerySchema, listVariablesResponseSchema} from '@shipfox/api-secrets-dto';
|
|
2
|
+
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
3
|
+
import {z} from 'zod';
|
|
4
|
+
import {listManagedVariables} from '#core/index.js';
|
|
5
|
+
import {toVariableListItemDto} from '#presentation/dto/index.js';
|
|
6
|
+
import {requireManagementRead} from './auth.js';
|
|
7
|
+
import {decodeManagementCursor, encodeManagementCursor} from './cursor.js';
|
|
8
|
+
import {translateManagementError} from './errors.js';
|
|
9
|
+
|
|
10
|
+
export const listVariablesRoute = defineRoute({
|
|
11
|
+
method: 'GET',
|
|
12
|
+
path: '/variables',
|
|
13
|
+
description: 'List user-managed variables for a workspace or project scope.',
|
|
14
|
+
schema: {
|
|
15
|
+
params: z.object({workspaceId: z.string().uuid()}),
|
|
16
|
+
querystring: listVariablesQuerySchema,
|
|
17
|
+
response: {200: listVariablesResponseSchema},
|
|
18
|
+
},
|
|
19
|
+
errorHandler: translateManagementError,
|
|
20
|
+
handler: async (request) => {
|
|
21
|
+
const {workspaceId} = request.params;
|
|
22
|
+
const {project_id: projectId, limit, cursor} = request.query;
|
|
23
|
+
const decodedCursor = decodeManagementCursor(cursor);
|
|
24
|
+
if (cursor && !decodedCursor) {
|
|
25
|
+
throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await requireManagementRead({request, workspaceId, projectId});
|
|
29
|
+
const result = await listManagedVariables({
|
|
30
|
+
workspaceId,
|
|
31
|
+
projectId,
|
|
32
|
+
limit,
|
|
33
|
+
cursor: decodedCursor ?? undefined,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
variables: result.variables.map(toVariableListItemDto),
|
|
38
|
+
next_cursor: result.nextCursor ? encodeManagementCursor(result.nextCursor) : null,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|