@shipfox/api-secrets 5.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +39 -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 -19
  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
@@ -3,39 +3,41 @@ import {ClientError, defineRoute} from '@shipfox/node-fastify';
3
3
  import {z} from 'zod';
4
4
  import {listManagedSecrets} from '#core/index.js';
5
5
  import {toSecretDto} from '#presentation/dto/index.js';
6
- import {requireManagementRead} from './auth.js';
6
+ import type {ManagementAccessHelpers} from './auth.js';
7
7
  import {decodeManagementCursor, encodeManagementCursor} from './cursor.js';
8
8
  import {translateManagementError} from './errors.js';
9
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
- }
10
+ export function listSecretsRoute(accessControl: ManagementAccessHelpers) {
11
+ return defineRoute({
12
+ method: 'GET',
13
+ path: '/secrets',
14
+ description: 'List user-managed secrets for a workspace or project scope.',
15
+ schema: {
16
+ params: z.object({workspaceId: z.string().uuid()}),
17
+ querystring: listSecretsQuerySchema,
18
+ response: {200: listSecretsResponseSchema},
19
+ },
20
+ errorHandler: translateManagementError,
21
+ handler: async (request) => {
22
+ const {workspaceId} = request.params;
23
+ const {project_id: projectId, limit, cursor} = request.query;
24
+ const decodedCursor = decodeManagementCursor(cursor);
25
+ if (cursor && !decodedCursor) {
26
+ throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});
27
+ }
27
28
 
28
- await requireManagementRead({request, workspaceId, projectId});
29
- const result = await listManagedSecrets({
30
- workspaceId,
31
- projectId,
32
- limit,
33
- cursor: decodedCursor ?? undefined,
34
- });
29
+ await accessControl.requireManagementRead({request, workspaceId, projectId});
30
+ const result = await listManagedSecrets({
31
+ workspaceId,
32
+ projectId,
33
+ limit,
34
+ cursor: decodedCursor ?? undefined,
35
+ });
35
36
 
36
- return {
37
- secrets: result.secrets.map(toSecretDto),
38
- next_cursor: result.nextCursor ? encodeManagementCursor(result.nextCursor) : null,
39
- };
40
- },
41
- });
37
+ return {
38
+ secrets: result.secrets.map(toSecretDto),
39
+ next_cursor: result.nextCursor ? encodeManagementCursor(result.nextCursor) : null,
40
+ };
41
+ },
42
+ });
43
+ }
@@ -3,39 +3,41 @@ import {ClientError, defineRoute} from '@shipfox/node-fastify';
3
3
  import {z} from 'zod';
4
4
  import {listManagedVariables} from '#core/index.js';
5
5
  import {toVariableListItemDto} from '#presentation/dto/index.js';
6
- import {requireManagementRead} from './auth.js';
6
+ import type {ManagementAccessHelpers} from './auth.js';
7
7
  import {decodeManagementCursor, encodeManagementCursor} from './cursor.js';
8
8
  import {translateManagementError} from './errors.js';
9
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
- }
10
+ export function listVariablesRoute(accessControl: ManagementAccessHelpers) {
11
+ return defineRoute({
12
+ method: 'GET',
13
+ path: '/variables',
14
+ description: 'List user-managed variables for a workspace or project scope.',
15
+ schema: {
16
+ params: z.object({workspaceId: z.string().uuid()}),
17
+ querystring: listVariablesQuerySchema,
18
+ response: {200: listVariablesResponseSchema},
19
+ },
20
+ errorHandler: translateManagementError,
21
+ handler: async (request) => {
22
+ const {workspaceId} = request.params;
23
+ const {project_id: projectId, limit, cursor} = request.query;
24
+ const decodedCursor = decodeManagementCursor(cursor);
25
+ if (cursor && !decodedCursor) {
26
+ throw new ClientError('Invalid cursor', 'invalid-cursor', {status: 400});
27
+ }
27
28
 
28
- await requireManagementRead({request, workspaceId, projectId});
29
- const result = await listManagedVariables({
30
- workspaceId,
31
- projectId,
32
- limit,
33
- cursor: decodedCursor ?? undefined,
34
- });
29
+ await accessControl.requireManagementRead({request, workspaceId, projectId});
30
+ const result = await listManagedVariables({
31
+ workspaceId,
32
+ projectId,
33
+ limit,
34
+ cursor: decodedCursor ?? undefined,
35
+ });
35
36
 
36
- return {
37
- variables: result.variables.map(toVariableListItemDto),
38
- next_cursor: result.nextCursor ? encodeManagementCursor(result.nextCursor) : null,
39
- };
40
- },
41
- });
37
+ return {
38
+ variables: result.variables.map(toVariableListItemDto),
39
+ next_cursor: result.nextCursor ? encodeManagementCursor(result.nextCursor) : null,
40
+ };
41
+ },
42
+ });
43
+ }
@@ -4,7 +4,7 @@ import {
4
4
  setUserContext,
5
5
  type UserContextMembership,
6
6
  } from '@shipfox/api-auth-context';
7
- import {ProjectNotFoundError, requireProjectForWorkspace} from '@shipfox/api-projects';
7
+ import {projectsInterModuleContract} from '@shipfox/api-projects-dto';
8
8
  import {
9
9
  SECRET_CREATED,
10
10
  SECRET_DELETED,
@@ -12,17 +12,22 @@ import {
12
12
  VARIABLE_CREATED,
13
13
  VARIABLE_DELETED,
14
14
  } from '@shipfox/api-secrets-dto';
15
+ import {createInterModuleKnownError, defineInterModulePresentation} from '@shipfox/inter-module';
15
16
  import type {AuthMethod, FastifyRequest} from '@shipfox/node-fastify';
16
17
  import {ClientError, closeApp, createApp} from '@shipfox/node-fastify';
18
+ import {createFakeInterModuleClients} from '@shipfox/node-module/inter-module/testing';
17
19
  import {and, eq, isNull, sql} from 'drizzle-orm';
18
20
  import {setSecrets} from '#core/index.js';
19
21
  import {db, secretsOutbox, secretValues, secretVariables} from '#db/index.js';
20
- import {secretsRoutes} from './index.js';
22
+ import {createSecretsRoutes} from './index.js';
21
23
 
22
- vi.mock('@shipfox/api-projects', async (importOriginal) => {
23
- const actual = await importOriginal<typeof import('@shipfox/api-projects')>();
24
- return {...actual, requireProjectForWorkspace: vi.fn()};
25
- });
24
+ const requireProjectForWorkspace = vi.fn();
25
+ const projects = createFakeInterModuleClients({
26
+ projects: defineInterModulePresentation(projectsInterModuleContract, {
27
+ getProjectById: () => ({project: null}),
28
+ requireProjectForWorkspace: async (input) => await requireProjectForWorkspace(input),
29
+ }),
30
+ }).projects;
26
31
 
27
32
  const USER_ID = '11111111-1111-4111-8111-111111111111';
28
33
  let authenticatedMemberships: UserContextMembership[] = [];
@@ -56,16 +61,21 @@ describe('secrets management routes', () => {
56
61
  workspaceId = crypto.randomUUID();
57
62
  projectId = crypto.randomUUID();
58
63
  authenticatedMemberships = [{workspaceId, role: 'admin'}];
59
- vi.mocked(requireProjectForWorkspace).mockResolvedValue({
60
- id: projectId,
61
- workspaceId,
62
- name: 'Project',
63
- sourceConnectionId: crypto.randomUUID(),
64
- sourceExternalRepositoryId: 'repo-1',
65
- createdAt: new Date(),
66
- updatedAt: new Date(),
64
+ requireProjectForWorkspace.mockResolvedValue({
65
+ project: {
66
+ id: projectId,
67
+ workspaceId,
68
+ name: 'Project',
69
+ sourceConnectionId: crypto.randomUUID(),
70
+ sourceExternalRepositoryId: 'repo-1',
71
+ createdAt: new Date(),
72
+ },
73
+ });
74
+ app = await createApp({
75
+ auth: [fakeUserAuth],
76
+ routes: createSecretsRoutes(projects),
77
+ swagger: false,
67
78
  });
68
- app = await createApp({auth: [fakeUserAuth], routes: secretsRoutes, swagger: false});
69
79
  await app.ready();
70
80
  });
71
81
 
@@ -76,7 +86,7 @@ describe('secrets management routes', () => {
76
86
  });
77
87
 
78
88
  it('registers management routes under user auth', () => {
79
- expect(secretsRoutes[0]?.auth).toBe(AUTH_USER);
89
+ expect(createSecretsRoutes(projects)[0]?.auth).toBe(AUTH_USER);
80
90
  });
81
91
 
82
92
  it('returns 401 unauthenticated and 403 for non-members', async () => {
@@ -423,8 +433,12 @@ describe('secrets management routes', () => {
423
433
  });
424
434
 
425
435
  it('returns 404 when project_id does not belong to the workspace', async () => {
426
- vi.mocked(requireProjectForWorkspace).mockRejectedValueOnce(
427
- new ProjectNotFoundError(projectId),
436
+ requireProjectForWorkspace.mockRejectedValueOnce(
437
+ createInterModuleKnownError(
438
+ projectsInterModuleContract.methods.requireProjectForWorkspace,
439
+ 'project-not-found',
440
+ {projectId},
441
+ ),
428
442
  );
429
443
 
430
444
  const res = await app.inject({
@@ -7,33 +7,35 @@ import {defineRoute} from '@shipfox/node-fastify';
7
7
  import {z} from 'zod';
8
8
  import {setManagedSecrets} from '#core/index.js';
9
9
  import {toSecretDto} from '#presentation/dto/index.js';
10
- import {requireManagementWrite} from './auth.js';
10
+ import type {ManagementAccessHelpers} from './auth.js';
11
11
  import {translateManagementError} from './errors.js';
12
12
  import {secretWarnings} from './warnings.js';
13
13
 
14
- export const putSecretRoute = defineRoute({
15
- method: 'PUT',
16
- path: '/secrets/:key',
17
- description: 'Create or update a user-managed secret.',
18
- schema: {
19
- params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
20
- body: putSecretBodySchema,
21
- response: {200: putSecretResponseSchema},
22
- },
23
- errorHandler: translateManagementError,
24
- handler: async (request) => {
25
- const {workspaceId, key} = request.params;
26
- const {project_id: projectId, value} = request.body;
27
- const access = await requireManagementWrite({request, workspaceId, projectId});
28
- const entries = [{key, value}];
29
- const [secret] = await setManagedSecrets({
30
- workspaceId,
31
- projectId,
32
- actorId: access.userId,
33
- entries,
34
- });
35
- if (!secret) throw new Error('Secret write returned no row');
14
+ export function putSecretRoute(accessControl: ManagementAccessHelpers) {
15
+ return defineRoute({
16
+ method: 'PUT',
17
+ path: '/secrets/:key',
18
+ description: 'Create or update a user-managed secret.',
19
+ schema: {
20
+ params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
21
+ body: putSecretBodySchema,
22
+ response: {200: putSecretResponseSchema},
23
+ },
24
+ errorHandler: translateManagementError,
25
+ handler: async (request) => {
26
+ const {workspaceId, key} = request.params;
27
+ const {project_id: projectId, value} = request.body;
28
+ const access = await accessControl.requireManagementWrite({request, workspaceId, projectId});
29
+ const entries = [{key, value}];
30
+ const [secret] = await setManagedSecrets({
31
+ workspaceId,
32
+ projectId,
33
+ actorId: access.userId,
34
+ entries,
35
+ });
36
+ if (!secret) throw new Error('Secret write returned no row');
36
37
 
37
- return {secret: toSecretDto(secret), warnings: secretWarnings(entries)};
38
- },
39
- });
38
+ return {secret: toSecretDto(secret), warnings: secretWarnings(entries)};
39
+ },
40
+ });
41
+ }
@@ -7,33 +7,35 @@ import {defineRoute} from '@shipfox/node-fastify';
7
7
  import {z} from 'zod';
8
8
  import {setManagedVariables} from '#core/index.js';
9
9
  import {toVariableDto} from '#presentation/dto/index.js';
10
- import {requireManagementWrite} from './auth.js';
10
+ import type {ManagementAccessHelpers} from './auth.js';
11
11
  import {translateManagementError} from './errors.js';
12
12
  import {variableWarnings} from './warnings.js';
13
13
 
14
- export const putVariableRoute = defineRoute({
15
- method: 'PUT',
16
- path: '/variables/:key',
17
- description: 'Create or update a user-managed variable.',
18
- schema: {
19
- params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
20
- body: putVariableBodySchema,
21
- response: {200: putVariableResponseSchema},
22
- },
23
- errorHandler: translateManagementError,
24
- handler: async (request) => {
25
- const {workspaceId, key} = request.params;
26
- const {project_id: projectId, value} = request.body;
27
- const access = await requireManagementWrite({request, workspaceId, projectId});
28
- const entries = [{key, value}];
29
- const [variable] = await setManagedVariables({
30
- workspaceId,
31
- projectId,
32
- actorId: access.userId,
33
- entries,
34
- });
35
- if (!variable) throw new Error('Variable write returned no row');
14
+ export function putVariableRoute(accessControl: ManagementAccessHelpers) {
15
+ return defineRoute({
16
+ method: 'PUT',
17
+ path: '/variables/:key',
18
+ description: 'Create or update a user-managed variable.',
19
+ schema: {
20
+ params: z.object({workspaceId: z.string().uuid(), key: secretKeySchema}),
21
+ body: putVariableBodySchema,
22
+ response: {200: putVariableResponseSchema},
23
+ },
24
+ errorHandler: translateManagementError,
25
+ handler: async (request) => {
26
+ const {workspaceId, key} = request.params;
27
+ const {project_id: projectId, value} = request.body;
28
+ const access = await accessControl.requireManagementWrite({request, workspaceId, projectId});
29
+ const entries = [{key, value}];
30
+ const [variable] = await setManagedVariables({
31
+ workspaceId,
32
+ projectId,
33
+ actorId: access.userId,
34
+ entries,
35
+ });
36
+ if (!variable) throw new Error('Variable write returned no row');
36
37
 
37
- return {variable: toVariableDto(variable), warnings: variableWarnings(entries)};
38
- },
39
- });
38
+ return {variable: toVariableDto(variable), warnings: variableWarnings(entries)};
39
+ },
40
+ });
41
+ }