@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.
Files changed (76) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +29 -15
  6. package/dist/index.js.map +1 -1
  7. package/dist/presentation/inter-module.d.ts +4 -0
  8. package/dist/presentation/inter-module.d.ts.map +1 -0
  9. package/dist/presentation/inter-module.js +73 -0
  10. package/dist/presentation/inter-module.js.map +1 -0
  11. package/dist/presentation/routes/auth.d.ts +14 -10
  12. package/dist/presentation/routes/auth.d.ts.map +1 -1
  13. package/dist/presentation/routes/auth.js +27 -11
  14. package/dist/presentation/routes/auth.js.map +1 -1
  15. package/dist/presentation/routes/batch-secrets.d.ts +2 -1
  16. package/dist/presentation/routes/batch-secrets.d.ts.map +1 -1
  17. package/dist/presentation/routes/batch-secrets.js +35 -34
  18. package/dist/presentation/routes/batch-secrets.js.map +1 -1
  19. package/dist/presentation/routes/batch-variables.d.ts +2 -1
  20. package/dist/presentation/routes/batch-variables.d.ts.map +1 -1
  21. package/dist/presentation/routes/batch-variables.js +35 -34
  22. package/dist/presentation/routes/batch-variables.js.map +1 -1
  23. package/dist/presentation/routes/delete-secret.d.ts +2 -1
  24. package/dist/presentation/routes/delete-secret.d.ts.map +1 -1
  25. package/dist/presentation/routes/delete-secret.js +33 -32
  26. package/dist/presentation/routes/delete-secret.js.map +1 -1
  27. package/dist/presentation/routes/delete-variable.d.ts +2 -1
  28. package/dist/presentation/routes/delete-variable.d.ts.map +1 -1
  29. package/dist/presentation/routes/delete-variable.js +33 -32
  30. package/dist/presentation/routes/delete-variable.js.map +1 -1
  31. package/dist/presentation/routes/errors.d.ts.map +1 -1
  32. package/dist/presentation/routes/errors.js +0 -6
  33. package/dist/presentation/routes/errors.js.map +1 -1
  34. package/dist/presentation/routes/get-variable.d.ts +2 -1
  35. package/dist/presentation/routes/get-variable.d.ts.map +1 -1
  36. package/dist/presentation/routes/get-variable.js +34 -33
  37. package/dist/presentation/routes/get-variable.js.map +1 -1
  38. package/dist/presentation/routes/index.d.ts +2 -1
  39. package/dist/presentation/routes/index.d.ts.map +1 -1
  40. package/dist/presentation/routes/index.js +21 -17
  41. package/dist/presentation/routes/index.js.map +1 -1
  42. package/dist/presentation/routes/list-secrets.d.ts +2 -1
  43. package/dist/presentation/routes/list-secrets.d.ts.map +1 -1
  44. package/dist/presentation/routes/list-secrets.js +40 -39
  45. package/dist/presentation/routes/list-secrets.js.map +1 -1
  46. package/dist/presentation/routes/list-variables.d.ts +2 -1
  47. package/dist/presentation/routes/list-variables.d.ts.map +1 -1
  48. package/dist/presentation/routes/list-variables.js +40 -39
  49. package/dist/presentation/routes/list-variables.js.map +1 -1
  50. package/dist/presentation/routes/put-secret.d.ts +2 -1
  51. package/dist/presentation/routes/put-secret.d.ts.map +1 -1
  52. package/dist/presentation/routes/put-secret.js +43 -42
  53. package/dist/presentation/routes/put-secret.js.map +1 -1
  54. package/dist/presentation/routes/put-variable.d.ts +2 -1
  55. package/dist/presentation/routes/put-variable.d.ts.map +1 -1
  56. package/dist/presentation/routes/put-variable.js +43 -42
  57. package/dist/presentation/routes/put-variable.js.map +1 -1
  58. package/dist/tsconfig.test.tsbuildinfo +1 -1
  59. package/package.json +11 -8
  60. package/src/index.ts +16 -5
  61. package/src/presentation/inter-module.test.ts +32 -0
  62. package/src/presentation/inter-module.ts +78 -0
  63. package/src/presentation/routes/auth.ts +65 -19
  64. package/src/presentation/routes/batch-secrets.ts +26 -24
  65. package/src/presentation/routes/batch-variables.ts +26 -24
  66. package/src/presentation/routes/delete-secret.ts +21 -19
  67. package/src/presentation/routes/delete-variable.ts +21 -19
  68. package/src/presentation/routes/errors.ts +0 -5
  69. package/src/presentation/routes/get-variable.ts +21 -19
  70. package/src/presentation/routes/index.ts +22 -17
  71. package/src/presentation/routes/list-secrets.ts +33 -31
  72. package/src/presentation/routes/list-variables.ts +33 -31
  73. package/src/presentation/routes/management.test.ts +32 -18
  74. package/src/presentation/routes/put-secret.ts +28 -26
  75. package/src/presentation/routes/put-variable.ts +28 -26
  76. 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": "5.0.0",
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": "5.0.0",
32
- "@shipfox/api-projects": "5.0.0",
33
- "@shipfox/api-secrets-dto": "5.0.0",
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/node-drizzle": "0.3.1",
36
- "@shipfox/node-fastify": "0.2.3",
37
- "@shipfox/node-module": "0.3.2",
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.3",
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 {secretsRoutes} from '#presentation/routes/index.js';
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 {requireProjectForWorkspace} from '@shipfox/api-projects';
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 async function requireManagementRead(params: {
12
- request: FastifyRequest;
13
- workspaceId: string;
14
- projectId?: string | undefined;
15
- }): Promise<ManagementAccess> {
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
- export async function requireManagementWrite(params: {
31
- request: FastifyRequest;
32
- workspaceId: string;
33
- projectId?: string | undefined;
34
- }): Promise<ManagementAccess> {
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 {requireManagementWrite} from './auth.js';
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 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
- });
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
- return {secrets: secrets.map(toSecretDto), warnings: secretWarnings(entries)};
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 {requireManagementWrite} from './auth.js';
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 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
- });
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
- return {variables: variables.map(toVariableDto), warnings: variableWarnings(entries)};
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 {requireManagementWrite} from './auth.js';
5
+ import type {ManagementAccessHelpers} from './auth.js';
6
6
  import {translateManagementError} from './errors.js';
7
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});
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
- await deleteManagedSecret({workspaceId, projectId, key, actorId: access.userId});
24
- return reply.status(204).send();
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 {requireManagementWrite} from './auth.js';
5
+ import type {ManagementAccessHelpers} from './auth.js';
6
6
  import {translateManagementError} from './errors.js';
7
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});
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
- await deleteManagedVariable({workspaceId, projectId, key, actorId: access.userId});
24
- return reply.status(204).send();
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 {requireManagementRead} from './auth.js';
10
+ import type {ManagementAccessHelpers} from './auth.js';
11
11
  import {translateManagementError} from './errors.js';
12
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;
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
- await requireManagementRead({request, workspaceId, projectId});
28
- const variable = await getManagedVariable({workspaceId, projectId, key});
28
+ await accessControl.requireManagementRead({request, workspaceId, projectId});
29
+ const variable = await getManagedVariable({workspaceId, projectId, key});
29
30
 
30
- return {variable: toVariableDto(variable)};
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 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
- ];
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
+ }