@shipfox/api-secrets 5.0.0 → 6.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 +1 -1
- package/CHANGELOG.md +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -15
- package/dist/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +4 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +73 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/routes/auth.d.ts +14 -10
- package/dist/presentation/routes/auth.d.ts.map +1 -1
- package/dist/presentation/routes/auth.js +27 -11
- package/dist/presentation/routes/auth.js.map +1 -1
- package/dist/presentation/routes/batch-secrets.d.ts +2 -1
- package/dist/presentation/routes/batch-secrets.d.ts.map +1 -1
- package/dist/presentation/routes/batch-secrets.js +35 -34
- package/dist/presentation/routes/batch-secrets.js.map +1 -1
- package/dist/presentation/routes/batch-variables.d.ts +2 -1
- package/dist/presentation/routes/batch-variables.d.ts.map +1 -1
- package/dist/presentation/routes/batch-variables.js +35 -34
- package/dist/presentation/routes/batch-variables.js.map +1 -1
- package/dist/presentation/routes/delete-secret.d.ts +2 -1
- package/dist/presentation/routes/delete-secret.d.ts.map +1 -1
- package/dist/presentation/routes/delete-secret.js +33 -32
- package/dist/presentation/routes/delete-secret.js.map +1 -1
- package/dist/presentation/routes/delete-variable.d.ts +2 -1
- package/dist/presentation/routes/delete-variable.d.ts.map +1 -1
- package/dist/presentation/routes/delete-variable.js +33 -32
- package/dist/presentation/routes/delete-variable.js.map +1 -1
- package/dist/presentation/routes/errors.d.ts.map +1 -1
- package/dist/presentation/routes/errors.js +0 -6
- package/dist/presentation/routes/errors.js.map +1 -1
- package/dist/presentation/routes/get-variable.d.ts +2 -1
- package/dist/presentation/routes/get-variable.d.ts.map +1 -1
- package/dist/presentation/routes/get-variable.js +34 -33
- package/dist/presentation/routes/get-variable.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -1
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +21 -17
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/list-secrets.d.ts +2 -1
- package/dist/presentation/routes/list-secrets.d.ts.map +1 -1
- package/dist/presentation/routes/list-secrets.js +40 -39
- package/dist/presentation/routes/list-secrets.js.map +1 -1
- package/dist/presentation/routes/list-variables.d.ts +2 -1
- package/dist/presentation/routes/list-variables.d.ts.map +1 -1
- package/dist/presentation/routes/list-variables.js +40 -39
- package/dist/presentation/routes/list-variables.js.map +1 -1
- package/dist/presentation/routes/put-secret.d.ts +2 -1
- package/dist/presentation/routes/put-secret.d.ts.map +1 -1
- package/dist/presentation/routes/put-secret.js +43 -42
- package/dist/presentation/routes/put-secret.js.map +1 -1
- package/dist/presentation/routes/put-variable.d.ts +2 -1
- package/dist/presentation/routes/put-variable.d.ts.map +1 -1
- package/dist/presentation/routes/put-variable.js +43 -42
- package/dist/presentation/routes/put-variable.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +11 -8
- package/src/index.ts +16 -5
- package/src/presentation/inter-module.test.ts +32 -0
- package/src/presentation/inter-module.ts +78 -0
- package/src/presentation/routes/auth.ts +65 -19
- package/src/presentation/routes/batch-secrets.ts +26 -24
- package/src/presentation/routes/batch-variables.ts +26 -24
- package/src/presentation/routes/delete-secret.ts +21 -19
- package/src/presentation/routes/delete-variable.ts +21 -19
- package/src/presentation/routes/errors.ts +0 -5
- package/src/presentation/routes/get-variable.ts +21 -19
- package/src/presentation/routes/index.ts +22 -17
- package/src/presentation/routes/list-secrets.ts +33 -31
- package/src/presentation/routes/list-variables.ts +33 -31
- package/src/presentation/routes/management.test.ts +32 -18
- package/src/presentation/routes/put-secret.ts +28 -26
- package/src/presentation/routes/put-variable.ts +28 -26
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-secrets",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"drizzle-orm": "^0.45.2",
|
|
30
30
|
"zod": "^4.4.3",
|
|
31
|
-
"@shipfox/api-auth-context": "
|
|
32
|
-
"@shipfox/api-projects": "
|
|
33
|
-
"@shipfox/api-secrets-dto": "
|
|
31
|
+
"@shipfox/api-auth-context": "6.0.0",
|
|
32
|
+
"@shipfox/api-projects-dto": "6.0.0",
|
|
33
|
+
"@shipfox/api-secrets-dto": "6.0.0",
|
|
34
34
|
"@shipfox/config": "1.2.2",
|
|
35
|
-
"@shipfox/
|
|
36
|
-
"@shipfox/node-
|
|
37
|
-
"@shipfox/node-
|
|
35
|
+
"@shipfox/inter-module": "0.2.0",
|
|
36
|
+
"@shipfox/node-drizzle": "0.3.2",
|
|
37
|
+
"@shipfox/node-fastify": "0.2.4",
|
|
38
|
+
"@shipfox/node-module": "0.4.0",
|
|
38
39
|
"@shipfox/node-opentelemetry": "0.5.2",
|
|
39
|
-
"@shipfox/node-outbox": "0.2.
|
|
40
|
+
"@shipfox/node-outbox": "0.2.4",
|
|
40
41
|
"@shipfox/node-postgres": "0.4.2"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"drizzle-kit": "^0.31.10",
|
|
45
46
|
"fastify": "^5.3.3",
|
|
46
47
|
"@shipfox/biome": "1.8.2",
|
|
48
|
+
"@shipfox/depcruise": "1.0.2",
|
|
47
49
|
"@shipfox/swc": "1.2.6",
|
|
48
50
|
"@shipfox/ts-config": "1.3.8",
|
|
49
51
|
"@shipfox/typescript": "1.1.7",
|
|
@@ -53,6 +55,7 @@
|
|
|
53
55
|
"build": "shipfox-swc",
|
|
54
56
|
"check": "shipfox-biome-check",
|
|
55
57
|
"check:fix": "shipfox-biome-check --write",
|
|
58
|
+
"depcruise": "shipfox-depcruise",
|
|
56
59
|
"test": "shipfox-vitest-run",
|
|
57
60
|
"test:watch": "shipfox-vitest-watch",
|
|
58
61
|
"type": "shipfox-tsc-check",
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
1
2
|
import {secretsEventSchemas} from '@shipfox/api-secrets-dto';
|
|
2
3
|
import type {ShipfoxModule} from '@shipfox/node-module';
|
|
3
4
|
import {db, migrationsPath, secretsOutbox} from '#db/index.js';
|
|
4
5
|
import {registerSecretsServiceMetrics} from '#metrics/index.js';
|
|
5
6
|
import {secretsE2eRoutes} from '#presentation/e2eRoutes/index.js';
|
|
6
|
-
import {
|
|
7
|
+
import {createSecretsInterModulePresentation} from '#presentation/inter-module.js';
|
|
8
|
+
import {createSecretsRoutes} from '#presentation/routes/index.js';
|
|
7
9
|
|
|
8
10
|
export {
|
|
9
11
|
BUILTIN_LOCAL_STORE,
|
|
@@ -40,11 +42,20 @@ export {
|
|
|
40
42
|
WorkspaceSecretCapExceededError,
|
|
41
43
|
} from '#core/index.js';
|
|
42
44
|
|
|
45
|
+
export function createSecretsModule(projects: ProjectsModuleClient): ShipfoxModule {
|
|
46
|
+
return {
|
|
47
|
+
name: 'secrets',
|
|
48
|
+
database: {db, migrationsPath},
|
|
49
|
+
routes: createSecretsRoutes(projects),
|
|
50
|
+
e2eRoutes: [secretsE2eRoutes],
|
|
51
|
+
metrics: registerSecretsServiceMetrics,
|
|
52
|
+
publishers: [{name: 'secrets', table: secretsOutbox, db, eventSchemas: secretsEventSchemas}],
|
|
53
|
+
interModulePresentations: [createSecretsInterModulePresentation()],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Migration setup imports this declaration only for the owned database metadata.
|
|
43
58
|
export const secretsModule: ShipfoxModule = {
|
|
44
59
|
name: 'secrets',
|
|
45
60
|
database: {db, migrationsPath},
|
|
46
|
-
routes: secretsRoutes,
|
|
47
|
-
e2eRoutes: [secretsE2eRoutes],
|
|
48
|
-
metrics: registerSecretsServiceMetrics,
|
|
49
|
-
publishers: [{name: 'secrets', table: secretsOutbox, db, eventSchemas: secretsEventSchemas}],
|
|
50
61
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import {secretsInterModuleContract} from '@shipfox/api-secrets-dto/inter-module';
|
|
3
|
+
import {createInMemoryInterModuleTransport} from '@shipfox/node-module/inter-module';
|
|
4
|
+
import {setVariables} from '#core/index.js';
|
|
5
|
+
import {createSecretsInterModulePresentation} from './inter-module.js';
|
|
6
|
+
|
|
7
|
+
describe('Secrets inter-module presentation', () => {
|
|
8
|
+
test('persists and reads scoped secret and variable values through the transport', async () => {
|
|
9
|
+
const workspaceId = crypto.randomUUID();
|
|
10
|
+
const projectId = crypto.randomUUID();
|
|
11
|
+
const transport = createInMemoryInterModuleTransport();
|
|
12
|
+
const client = transport.createClient(secretsInterModuleContract);
|
|
13
|
+
transport.register(createSecretsInterModulePresentation());
|
|
14
|
+
transport.seal();
|
|
15
|
+
|
|
16
|
+
await client.setSecrets({
|
|
17
|
+
workspaceId,
|
|
18
|
+
projectId,
|
|
19
|
+
values: {TOKEN: 'project-value'},
|
|
20
|
+
});
|
|
21
|
+
const secret = await client.getSecret({workspaceId, projectId, key: 'TOKEN'});
|
|
22
|
+
await setVariables({workspaceId, projectId, values: {REGION: 'eu-west-3'}});
|
|
23
|
+
const namespace = await client.getSecretsByNamespace({workspaceId, projectId});
|
|
24
|
+
const variables = await client.getVariablesByNamespace({workspaceId, projectId});
|
|
25
|
+
const deleted = await client.deleteSecrets({workspaceId, projectId, keys: ['TOKEN']});
|
|
26
|
+
|
|
27
|
+
expect(secret).toEqual({value: 'project-value'});
|
|
28
|
+
expect(namespace).toEqual({values: {TOKEN: 'project-value'}});
|
|
29
|
+
expect(variables).toEqual({values: {REGION: 'eu-west-3'}});
|
|
30
|
+
expect(deleted).toEqual({deleted: 1});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {secretsInterModuleContract} from '@shipfox/api-secrets-dto/inter-module';
|
|
2
|
+
import {
|
|
3
|
+
createInterModuleKnownError,
|
|
4
|
+
defineInterModulePresentation,
|
|
5
|
+
type InterModulePresentation,
|
|
6
|
+
} from '@shipfox/inter-module';
|
|
7
|
+
import {
|
|
8
|
+
deleteSecrets,
|
|
9
|
+
getSecret,
|
|
10
|
+
getSecretsByNamespace,
|
|
11
|
+
getVariablesByNamespace,
|
|
12
|
+
SecretDecryptionError,
|
|
13
|
+
SecretValueTooLargeError,
|
|
14
|
+
setSecrets,
|
|
15
|
+
WorkspaceSecretCapExceededError,
|
|
16
|
+
} from '#core/index.js';
|
|
17
|
+
|
|
18
|
+
export function createSecretsInterModulePresentation(): InterModulePresentation<
|
|
19
|
+
typeof secretsInterModuleContract
|
|
20
|
+
> {
|
|
21
|
+
return defineInterModulePresentation(secretsInterModuleContract, {
|
|
22
|
+
getSecret: async (input) => {
|
|
23
|
+
try {
|
|
24
|
+
const {store, ...params} = input;
|
|
25
|
+
return {value: await getSecret({...params, ...(store === undefined ? {} : {store})})};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw toGetKnownError('getSecret', error);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
getSecretsByNamespace: async (input) => {
|
|
31
|
+
try {
|
|
32
|
+
const {store, ...params} = input;
|
|
33
|
+
return {
|
|
34
|
+
values: await getSecretsByNamespace({...params, ...(store === undefined ? {} : {store})}),
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw toGetKnownError('getSecretsByNamespace', error);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
getVariablesByNamespace: async (input) => ({
|
|
41
|
+
values: await getVariablesByNamespace(input),
|
|
42
|
+
}),
|
|
43
|
+
setSecrets: async (input) => {
|
|
44
|
+
try {
|
|
45
|
+
await setSecrets(input);
|
|
46
|
+
return {};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw toSetKnownError(error);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
deleteSecrets: async (input) => ({deleted: await deleteSecrets(input)}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toGetKnownError(
|
|
56
|
+
methodName: 'getSecret' | 'getSecretsByNamespace',
|
|
57
|
+
error: unknown,
|
|
58
|
+
): unknown {
|
|
59
|
+
if (error instanceof SecretDecryptionError) {
|
|
60
|
+
return createInterModuleKnownError(
|
|
61
|
+
secretsInterModuleContract.methods[methodName],
|
|
62
|
+
'secret-decryption-failed',
|
|
63
|
+
{},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return error;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toSetKnownError(error: unknown): unknown {
|
|
70
|
+
const method = secretsInterModuleContract.methods.setSecrets;
|
|
71
|
+
if (error instanceof SecretValueTooLargeError) {
|
|
72
|
+
return createInterModuleKnownError(method, 'value-too-large', {maxBytes: error.maxBytes});
|
|
73
|
+
}
|
|
74
|
+
if (error instanceof WorkspaceSecretCapExceededError) {
|
|
75
|
+
return createInterModuleKnownError(method, 'workspace-secret-cap-exceeded', {cap: error.cap});
|
|
76
|
+
}
|
|
77
|
+
return error;
|
|
78
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {requireWorkspaceAccess} from '@shipfox/api-auth-context';
|
|
2
|
-
import {
|
|
2
|
+
import {type ProjectsModuleClient, projectsInterModuleContract} from '@shipfox/api-projects-dto';
|
|
3
|
+
import {isInterModuleKnownError} from '@shipfox/inter-module';
|
|
3
4
|
import {ClientError} from '@shipfox/node-fastify';
|
|
4
5
|
import type {FastifyRequest} from 'fastify';
|
|
5
6
|
|
|
@@ -8,30 +9,61 @@ export interface ManagementAccess {
|
|
|
8
9
|
userId: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
export interface ManagementAccessHelpers {
|
|
13
|
+
requireManagementRead(params: {
|
|
14
|
+
request: FastifyRequest;
|
|
15
|
+
workspaceId: string;
|
|
16
|
+
projectId?: string | undefined;
|
|
17
|
+
}): Promise<ManagementAccess>;
|
|
18
|
+
requireManagementWrite(params: {
|
|
19
|
+
request: FastifyRequest;
|
|
20
|
+
workspaceId: string;
|
|
21
|
+
projectId?: string | undefined;
|
|
22
|
+
}): Promise<ManagementAccess>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createManagementAccess(projects: ProjectsModuleClient): ManagementAccessHelpers {
|
|
26
|
+
return {
|
|
27
|
+
requireManagementRead: (params: {
|
|
28
|
+
request: FastifyRequest;
|
|
29
|
+
workspaceId: string;
|
|
30
|
+
projectId?: string | undefined;
|
|
31
|
+
}) => requireManagementRead(params, projects),
|
|
32
|
+
requireManagementWrite: (params: {
|
|
33
|
+
request: FastifyRequest;
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
projectId?: string | undefined;
|
|
36
|
+
}) => requireManagementWrite(params, projects),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function requireManagementRead(
|
|
41
|
+
params: {
|
|
42
|
+
request: FastifyRequest;
|
|
43
|
+
workspaceId: string;
|
|
44
|
+
projectId?: string | undefined;
|
|
45
|
+
},
|
|
46
|
+
projects: ProjectsModuleClient,
|
|
47
|
+
): Promise<ManagementAccess> {
|
|
16
48
|
const membership = requireWorkspaceAccess({
|
|
17
49
|
request: params.request,
|
|
18
50
|
workspaceId: params.workspaceId,
|
|
19
51
|
});
|
|
20
52
|
if (params.projectId) {
|
|
21
|
-
await requireProjectForWorkspace(
|
|
22
|
-
workspaceId: params.workspaceId,
|
|
23
|
-
projectId: params.projectId,
|
|
24
|
-
});
|
|
53
|
+
await requireProjectForWorkspace(params.workspaceId, params.projectId, projects);
|
|
25
54
|
}
|
|
26
55
|
|
|
27
56
|
return {workspaceId: params.workspaceId, userId: membership.userId};
|
|
28
57
|
}
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
59
|
+
async function requireManagementWrite(
|
|
60
|
+
params: {
|
|
61
|
+
request: FastifyRequest;
|
|
62
|
+
workspaceId: string;
|
|
63
|
+
projectId?: string | undefined;
|
|
64
|
+
},
|
|
65
|
+
projects: ProjectsModuleClient,
|
|
66
|
+
): Promise<ManagementAccess> {
|
|
35
67
|
const membership = requireWorkspaceAccess({
|
|
36
68
|
request: params.request,
|
|
37
69
|
workspaceId: params.workspaceId,
|
|
@@ -40,11 +72,25 @@ export async function requireManagementWrite(params: {
|
|
|
40
72
|
throw new ClientError('Workspace admin role required', 'forbidden', {status: 403});
|
|
41
73
|
}
|
|
42
74
|
if (params.projectId) {
|
|
43
|
-
await requireProjectForWorkspace(
|
|
44
|
-
workspaceId: params.workspaceId,
|
|
45
|
-
projectId: params.projectId,
|
|
46
|
-
});
|
|
75
|
+
await requireProjectForWorkspace(params.workspaceId, params.projectId, projects);
|
|
47
76
|
}
|
|
48
77
|
|
|
49
78
|
return {workspaceId: params.workspaceId, userId: membership.userId};
|
|
50
79
|
}
|
|
80
|
+
|
|
81
|
+
async function requireProjectForWorkspace(
|
|
82
|
+
workspaceId: string,
|
|
83
|
+
projectId: string,
|
|
84
|
+
projects: ProjectsModuleClient,
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
try {
|
|
87
|
+
await projects.requireProjectForWorkspace({workspaceId, projectId});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (
|
|
90
|
+
isInterModuleKnownError(projectsInterModuleContract.methods.requireProjectForWorkspace, error)
|
|
91
|
+
) {
|
|
92
|
+
throw new ClientError('Project not found', 'project-not-found', {status: 404});
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -3,31 +3,33 @@ import {defineRoute} from '@shipfox/node-fastify';
|
|
|
3
3
|
import {z} from 'zod';
|
|
4
4
|
import {setManagedSecrets} from '#core/index.js';
|
|
5
5
|
import {toSecretDto} from '#presentation/dto/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import type {ManagementAccessHelpers} from './auth.js';
|
|
7
7
|
import {translateManagementError} from './errors.js';
|
|
8
8
|
import {secretWarnings} from './warnings.js';
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
export function batchSecretsRoute(accessControl: ManagementAccessHelpers) {
|
|
11
|
+
return defineRoute({
|
|
12
|
+
method: 'POST',
|
|
13
|
+
path: '/secrets::batch',
|
|
14
|
+
description: 'Create or update multiple user-managed secrets in one scope.',
|
|
15
|
+
schema: {
|
|
16
|
+
params: z.object({workspaceId: z.string().uuid()}),
|
|
17
|
+
body: batchSecretsBodySchema,
|
|
18
|
+
response: {200: batchSecretsResponseSchema},
|
|
19
|
+
},
|
|
20
|
+
errorHandler: translateManagementError,
|
|
21
|
+
handler: async (request) => {
|
|
22
|
+
const {workspaceId} = request.params;
|
|
23
|
+
const {project_id: projectId, entries} = request.body;
|
|
24
|
+
const access = await accessControl.requireManagementWrite({request, workspaceId, projectId});
|
|
25
|
+
const secrets = await setManagedSecrets({
|
|
26
|
+
workspaceId,
|
|
27
|
+
projectId,
|
|
28
|
+
actorId: access.userId,
|
|
29
|
+
entries,
|
|
30
|
+
});
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
32
|
+
return {secrets: secrets.map(toSecretDto), warnings: secretWarnings(entries)};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -3,31 +3,33 @@ import {defineRoute} from '@shipfox/node-fastify';
|
|
|
3
3
|
import {z} from 'zod';
|
|
4
4
|
import {setManagedVariables} from '#core/index.js';
|
|
5
5
|
import {toVariableDto} from '#presentation/dto/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import type {ManagementAccessHelpers} from './auth.js';
|
|
7
7
|
import {translateManagementError} from './errors.js';
|
|
8
8
|
import {variableWarnings} from './warnings.js';
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
export function batchVariablesRoute(accessControl: ManagementAccessHelpers) {
|
|
11
|
+
return defineRoute({
|
|
12
|
+
method: 'POST',
|
|
13
|
+
path: '/variables::batch',
|
|
14
|
+
description: 'Create or update multiple user-managed variables in one scope.',
|
|
15
|
+
schema: {
|
|
16
|
+
params: z.object({workspaceId: z.string().uuid()}),
|
|
17
|
+
body: batchVariablesBodySchema,
|
|
18
|
+
response: {200: batchVariablesResponseSchema},
|
|
19
|
+
},
|
|
20
|
+
errorHandler: translateManagementError,
|
|
21
|
+
handler: async (request) => {
|
|
22
|
+
const {workspaceId} = request.params;
|
|
23
|
+
const {project_id: projectId, entries} = request.body;
|
|
24
|
+
const access = await accessControl.requireManagementWrite({request, workspaceId, projectId});
|
|
25
|
+
const variables = await setManagedVariables({
|
|
26
|
+
workspaceId,
|
|
27
|
+
projectId,
|
|
28
|
+
actorId: access.userId,
|
|
29
|
+
entries,
|
|
30
|
+
});
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
32
|
+
return {variables: variables.map(toVariableDto), warnings: variableWarnings(entries)};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -2,25 +2,27 @@ import {secretKeySchema, secretScopeQuerySchema} from '@shipfox/api-secrets-dto'
|
|
|
2
2
|
import {defineRoute} from '@shipfox/node-fastify';
|
|
3
3
|
import {z} from 'zod';
|
|
4
4
|
import {deleteManagedSecret} from '#core/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import type {ManagementAccessHelpers} from './auth.js';
|
|
6
6
|
import {translateManagementError} from './errors.js';
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
export function deleteSecretRoute(accessControl: ManagementAccessHelpers) {
|
|
9
|
+
return defineRoute({
|
|
10
|
+
method: 'DELETE',
|
|
11
|
+
path: '/secrets/:key',
|
|
12
|
+
description: 'Delete a user-managed secret.',
|
|
13
|
+
schema: {
|
|
14
|
+
params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
|
|
15
|
+
querystring: secretScopeQuerySchema,
|
|
16
|
+
response: {204: z.void()},
|
|
17
|
+
},
|
|
18
|
+
errorHandler: translateManagementError,
|
|
19
|
+
handler: async (request, reply) => {
|
|
20
|
+
const {workspaceId, key} = request.params;
|
|
21
|
+
const {project_id: projectId} = request.query;
|
|
22
|
+
const access = await accessControl.requireManagementWrite({request, workspaceId, projectId});
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
24
|
+
await deleteManagedSecret({workspaceId, projectId, key, actorId: access.userId});
|
|
25
|
+
return reply.status(204).send();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -2,25 +2,27 @@ import {secretKeySchema, secretScopeQuerySchema} from '@shipfox/api-secrets-dto'
|
|
|
2
2
|
import {defineRoute} from '@shipfox/node-fastify';
|
|
3
3
|
import {z} from 'zod';
|
|
4
4
|
import {deleteManagedVariable} from '#core/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import type {ManagementAccessHelpers} from './auth.js';
|
|
6
6
|
import {translateManagementError} from './errors.js';
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
export function deleteVariableRoute(accessControl: ManagementAccessHelpers) {
|
|
9
|
+
return defineRoute({
|
|
10
|
+
method: 'DELETE',
|
|
11
|
+
path: '/variables/:key',
|
|
12
|
+
description: 'Delete a user-managed variable.',
|
|
13
|
+
schema: {
|
|
14
|
+
params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
|
|
15
|
+
querystring: secretScopeQuerySchema,
|
|
16
|
+
response: {204: z.void()},
|
|
17
|
+
},
|
|
18
|
+
errorHandler: translateManagementError,
|
|
19
|
+
handler: async (request, reply) => {
|
|
20
|
+
const {workspaceId, key} = request.params;
|
|
21
|
+
const {project_id: projectId} = request.query;
|
|
22
|
+
const access = await accessControl.requireManagementWrite({request, workspaceId, projectId});
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
24
|
+
await deleteManagedVariable({workspaceId, projectId, key, actorId: access.userId});
|
|
25
|
+
return reply.status(204).send();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {ProjectNotFoundError} from '@shipfox/api-projects';
|
|
2
1
|
import {ClientError} from '@shipfox/node-fastify';
|
|
3
2
|
import {
|
|
4
3
|
NamespaceValidationError,
|
|
@@ -55,9 +54,5 @@ export function translateManagementError(error: unknown): never {
|
|
|
55
54
|
if (error instanceof UnknownSecretStoreError) {
|
|
56
55
|
throw new ClientError('Unknown secret store', 'unknown-secret-store', {status: 400});
|
|
57
56
|
}
|
|
58
|
-
if (error instanceof ProjectNotFoundError) {
|
|
59
|
-
throw new ClientError('Project not found', 'project-not-found', {status: 404});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
57
|
throw error;
|
|
63
58
|
}
|
|
@@ -7,26 +7,28 @@ import {defineRoute} from '@shipfox/node-fastify';
|
|
|
7
7
|
import {z} from 'zod';
|
|
8
8
|
import {getManagedVariable} from '#core/index.js';
|
|
9
9
|
import {toVariableDto} from '#presentation/dto/index.js';
|
|
10
|
-
import {
|
|
10
|
+
import type {ManagementAccessHelpers} from './auth.js';
|
|
11
11
|
import {translateManagementError} from './errors.js';
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
export function getVariableRoute(accessControl: ManagementAccessHelpers) {
|
|
14
|
+
return defineRoute({
|
|
15
|
+
method: 'GET',
|
|
16
|
+
path: '/variables/:key',
|
|
17
|
+
description: 'Read a user-managed variable.',
|
|
18
|
+
schema: {
|
|
19
|
+
params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
|
|
20
|
+
querystring: secretScopeQuerySchema,
|
|
21
|
+
response: {200: getVariableResponseSchema},
|
|
22
|
+
},
|
|
23
|
+
errorHandler: translateManagementError,
|
|
24
|
+
handler: async (request) => {
|
|
25
|
+
const {workspaceId, key} = request.params;
|
|
26
|
+
const {project_id: projectId} = request.query;
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
await accessControl.requireManagementRead({request, workspaceId, projectId});
|
|
29
|
+
const variable = await getManagedVariable({workspaceId, projectId, key});
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
31
|
+
return {variable: toVariableDto(variable)};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {AUTH_USER} from '@shipfox/api-auth-context';
|
|
2
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
2
3
|
import type {RouteGroup} from '@shipfox/node-fastify';
|
|
4
|
+
import {createManagementAccess} from './auth.js';
|
|
3
5
|
import {batchSecretsRoute} from './batch-secrets.js';
|
|
4
6
|
import {batchVariablesRoute} from './batch-variables.js';
|
|
5
7
|
import {deleteSecretRoute} from './delete-secret.js';
|
|
@@ -10,20 +12,23 @@ import {listVariablesRoute} from './list-variables.js';
|
|
|
10
12
|
import {putSecretRoute} from './put-secret.js';
|
|
11
13
|
import {putVariableRoute} from './put-variable.js';
|
|
12
14
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]
|
|
15
|
+
export function createSecretsRoutes(projects: ProjectsModuleClient): RouteGroup[] {
|
|
16
|
+
const access = createManagementAccess(projects);
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
prefix: '/workspaces/:workspaceId',
|
|
20
|
+
auth: AUTH_USER,
|
|
21
|
+
routes: [
|
|
22
|
+
listSecretsRoute(access),
|
|
23
|
+
putSecretRoute(access),
|
|
24
|
+
batchSecretsRoute(access),
|
|
25
|
+
deleteSecretRoute(access),
|
|
26
|
+
listVariablesRoute(access),
|
|
27
|
+
getVariableRoute(access),
|
|
28
|
+
putVariableRoute(access),
|
|
29
|
+
batchVariablesRoute(access),
|
|
30
|
+
deleteVariableRoute(access),
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
}
|