@shipfox/api-auth-context 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.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 1 file with swc (98.13ms)
2
+ Successfully compiled: 1 file with swc (185.08ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @shipfox/api-auth-context
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 8bdc149: Adds scoped workspace and installation provisioner identities with explicit authorization boundaries.
8
+
9
+ ### Minor Changes
10
+
11
+ - b00ed29: Adds runner bootstrap enrollment and isolated pre-workspace control sessions.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [e6eba5b]
16
+ - Updated dependencies [ba2e3dc]
17
+ - Updated dependencies [1b79cda]
18
+ - Updated dependencies [c2db8c3]
19
+ - Updated dependencies [112c0fa]
20
+ - Updated dependencies [8aa7cd3]
21
+ - Updated dependencies [326f4c0]
22
+ - Updated dependencies [4a91956]
23
+ - @shipfox/api-auth-dto@6.0.0
24
+ - @shipfox/api-workspaces-dto@6.0.0
25
+ - @shipfox/node-fastify@0.2.4
26
+
3
27
  ## 5.0.0
4
28
 
5
29
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { WorkspaceRole } from '@shipfox/api-workspaces-dto';
3
3
  export declare const AUTH_USER = "user";
4
4
  export declare const AUTH_RUNNER_REGISTRATION_TOKEN = "runner-registration-token";
5
5
  export declare const AUTH_RUNNER_SESSION = "runner-session";
6
+ export declare const AUTH_RUNNER_CONTROL_SESSION = "runner-control-session";
6
7
  export declare const AUTH_LEASED_JOB = "leased-job";
7
8
  export declare const AUTH_PROVISIONER_TOKEN = "provisioner-token";
8
9
  export type WorkspaceStatus = 'active' | 'suspended' | 'deleted';
@@ -25,12 +26,21 @@ export interface BuildUserContextParams {
25
26
  memberships?: ReadonlyArray<UserContextMembership> | undefined;
26
27
  }
27
28
  export declare function buildUserContext(params: BuildUserContextParams): UserContext;
28
- export interface ProvisionerContext {
29
+ export type ProvisionerContext = {
29
30
  provisionerTokenId: string;
31
+ scope: 'installation';
32
+ } | {
33
+ provisionerTokenId: string;
34
+ scope: 'workspace';
30
35
  workspaceId: string;
31
- }
36
+ };
32
37
  export type LeasedJobContext = JobLeaseTokenClaims;
33
38
  export type RunnerSessionContext = RunnerSessionTokenClaims;
39
+ export interface RunnerControlSessionContext {
40
+ runnerControlSessionId: string;
41
+ runnerInstanceId: string;
42
+ provisionerId: string;
43
+ }
34
44
  type RequestWithContext = object;
35
45
  export declare function setUserContext(request: RequestWithContext, context: UserContext): void;
36
46
  export declare function getUserContext(request: RequestWithContext): UserContext | null;
@@ -53,11 +63,19 @@ export declare function requireWorkspaceAccess(params: RequireWorkspaceAccessPar
53
63
  export declare function setProvisionerContext(request: RequestWithContext, context: ProvisionerContext): void;
54
64
  export declare function getProvisionerContext(request: RequestWithContext): ProvisionerContext | null;
55
65
  export declare function requireProvisionerContext(request: RequestWithContext): ProvisionerContext;
66
+ export declare function requireWorkspaceProvisionerContext(request: RequestWithContext): Extract<ProvisionerContext, {
67
+ scope: 'workspace';
68
+ }>;
69
+ export declare function requireInstallationProvisionerContext(request: RequestWithContext): Extract<ProvisionerContext, {
70
+ scope: 'installation';
71
+ }>;
56
72
  export declare function setLeasedJobContext(request: RequestWithContext, context: LeasedJobContext): void;
57
73
  export declare function getLeasedJobContext(request: RequestWithContext): LeasedJobContext | null;
58
74
  export declare function requireLeasedJobContext(request: RequestWithContext): LeasedJobContext;
59
75
  export declare function setRunnerSessionContext(request: RequestWithContext, context: RunnerSessionContext): void;
60
76
  export declare function getRunnerSessionContext(request: RequestWithContext): RunnerSessionContext | null;
61
77
  export declare function requireRunnerSessionContext(request: RequestWithContext): RunnerSessionContext;
78
+ export declare function setRunnerControlSessionContext(request: RequestWithContext, context: RunnerControlSessionContext): void;
79
+ export declare function requireRunnerControlSessionContext(request: RequestWithContext): RunnerControlSessionContext;
62
80
  export {};
63
81
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { ClientError } from '@shipfox/node-fastify';
2
2
  export const AUTH_USER = 'user';
3
3
  export const AUTH_RUNNER_REGISTRATION_TOKEN = 'runner-registration-token';
4
4
  export const AUTH_RUNNER_SESSION = 'runner-session';
5
+ export const AUTH_RUNNER_CONTROL_SESSION = 'runner-control-session';
5
6
  export const AUTH_LEASED_JOB = 'leased-job';
6
7
  export const AUTH_PROVISIONER_TOKEN = 'provisioner-token';
7
8
  export function buildUserContext(params) {
@@ -19,6 +20,7 @@ const USER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/user');
19
20
  const LEASED_JOB_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/leased-job');
20
21
  const PROVISIONER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/provisioner');
21
22
  const RUNNER_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/runner-session');
23
+ const RUNNER_CONTROL_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/runner-control-session');
22
24
  export function setUserContext(request, context) {
23
25
  request[USER_CONTEXT_KEY] = context;
24
26
  }
@@ -68,6 +70,24 @@ export function requireProvisionerContext(request) {
68
70
  }
69
71
  return context;
70
72
  }
73
+ export function requireWorkspaceProvisionerContext(request) {
74
+ const context = requireProvisionerContext(request);
75
+ if (context.scope !== 'workspace') {
76
+ throw new ClientError('Workspace provisioner credential required', 'forbidden', {
77
+ status: 403
78
+ });
79
+ }
80
+ return context;
81
+ }
82
+ export function requireInstallationProvisionerContext(request) {
83
+ const context = requireProvisionerContext(request);
84
+ if (context.scope !== 'installation') {
85
+ throw new ClientError('Installation provisioner credential required', 'forbidden', {
86
+ status: 403
87
+ });
88
+ }
89
+ return context;
90
+ }
71
91
  export function setLeasedJobContext(request, context) {
72
92
  request[LEASED_JOB_CONTEXT_KEY] = context;
73
93
  }
@@ -94,5 +114,13 @@ export function requireRunnerSessionContext(request) {
94
114
  }
95
115
  return context;
96
116
  }
117
+ export function setRunnerControlSessionContext(request, context) {
118
+ request[RUNNER_CONTROL_SESSION_CONTEXT_KEY] = context;
119
+ }
120
+ export function requireRunnerControlSessionContext(request) {
121
+ const context = request[RUNNER_CONTROL_SESSION_CONTEXT_KEY];
122
+ if (!context) throw new Error('Runner control session context is not available on this request');
123
+ return context;
124
+ }
97
125
 
98
126
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nimport type {WorkspaceRole} from '@shipfox/api-workspaces-dto';\nimport {ClientError} from '@shipfox/node-fastify';\n\nexport const AUTH_USER = 'user';\nexport const AUTH_RUNNER_REGISTRATION_TOKEN = 'runner-registration-token';\nexport const AUTH_RUNNER_SESSION = 'runner-session';\nexport const AUTH_LEASED_JOB = 'leased-job';\nexport const AUTH_PROVISIONER_TOKEN = 'provisioner-token';\n\nexport type WorkspaceStatus = 'active' | 'suspended' | 'deleted';\n\nexport interface UserContextMembership {\n workspaceId: string;\n role: WorkspaceRole;\n}\n\nexport interface UserContext {\n userId: string;\n email: string;\n name: string | null;\n memberships: ReadonlyArray<UserContextMembership>;\n canAccess(workspaceId: string): boolean;\n hasRole(workspaceId: string, role: WorkspaceRole): boolean;\n}\n\nexport interface BuildUserContextParams {\n userId: string;\n email: string;\n name?: string | null | undefined;\n memberships?: ReadonlyArray<UserContextMembership> | undefined;\n}\n\nexport function buildUserContext(params: BuildUserContextParams): UserContext {\n const memberships = params.memberships ?? [];\n return {\n userId: params.userId,\n email: params.email,\n name: params.name ?? null,\n memberships,\n canAccess: (workspaceId) => memberships.some((m) => m.workspaceId === workspaceId),\n hasRole: (workspaceId, role) =>\n memberships.some((m) => m.workspaceId === workspaceId && m.role === role),\n };\n}\n\nexport interface ProvisionerContext {\n provisionerTokenId: string;\n workspaceId: string;\n}\n\nexport type LeasedJobContext = JobLeaseTokenClaims;\nexport type RunnerSessionContext = RunnerSessionTokenClaims;\n\ntype RequestWithContext = object;\n\nconst USER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/user');\nconst LEASED_JOB_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/leased-job');\nconst PROVISIONER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/provisioner');\nconst RUNNER_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/runner-session');\n\nexport function setUserContext(request: RequestWithContext, context: UserContext): void {\n (request as Record<symbol, unknown>)[USER_CONTEXT_KEY] = context;\n}\n\nexport function getUserContext(request: RequestWithContext): UserContext | null {\n return (\n ((request as Record<symbol, unknown>)[USER_CONTEXT_KEY] as UserContext | undefined) ?? null\n );\n}\n\nexport function requireUserContext(request: RequestWithContext): UserContext {\n const context = getUserContext(request);\n if (!context) {\n throw new Error('User context is not available on this request');\n }\n return context;\n}\n\nexport interface RequireWorkspaceAccessParams {\n request: RequestWithContext;\n workspaceId: string;\n}\n\nexport interface RequireWorkspaceAccessResult {\n workspaceId: string;\n userId: string;\n role: WorkspaceRole;\n}\n\n/**\n * Authorizes the request's user for a workspace using only the verified session\n * token. Membership and role come from the token claims, so the check stays\n * stateless and does not read the database on the request path.\n */\nexport function requireWorkspaceAccess(\n params: RequireWorkspaceAccessParams,\n): RequireWorkspaceAccessResult {\n const context = getUserContext(params.request);\n if (!context) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const membership = context.memberships.find((m) => m.workspaceId === params.workspaceId);\n if (!membership) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n\n return {workspaceId: params.workspaceId, userId: context.userId, role: membership.role};\n}\n\nexport function setProvisionerContext(\n request: RequestWithContext,\n context: ProvisionerContext,\n): void {\n (request as Record<symbol, unknown>)[PROVISIONER_CONTEXT_KEY] = context;\n}\n\nexport function getProvisionerContext(request: RequestWithContext): ProvisionerContext | null {\n return (\n ((request as Record<symbol, unknown>)[PROVISIONER_CONTEXT_KEY] as\n | ProvisionerContext\n | undefined) ?? null\n );\n}\n\nexport function requireProvisionerContext(request: RequestWithContext): ProvisionerContext {\n const context = getProvisionerContext(request);\n if (!context) {\n throw new Error('Provisioner context is not available on this request');\n }\n return context;\n}\n\nexport function setLeasedJobContext(request: RequestWithContext, context: LeasedJobContext): void {\n (request as Record<symbol, unknown>)[LEASED_JOB_CONTEXT_KEY] = context;\n}\n\nexport function getLeasedJobContext(request: RequestWithContext): LeasedJobContext | null {\n return (\n ((request as Record<symbol, unknown>)[LEASED_JOB_CONTEXT_KEY] as\n | LeasedJobContext\n | undefined) ?? null\n );\n}\n\nexport function requireLeasedJobContext(request: RequestWithContext): LeasedJobContext {\n const context = getLeasedJobContext(request);\n if (!context) {\n throw new Error('Leased job context is not available on this request');\n }\n return context;\n}\n\nexport function setRunnerSessionContext(\n request: RequestWithContext,\n context: RunnerSessionContext,\n): void {\n (request as Record<symbol, unknown>)[RUNNER_SESSION_CONTEXT_KEY] = context;\n}\n\nexport function getRunnerSessionContext(request: RequestWithContext): RunnerSessionContext | null {\n return (\n ((request as Record<symbol, unknown>)[RUNNER_SESSION_CONTEXT_KEY] as\n | RunnerSessionContext\n | undefined) ?? null\n );\n}\n\nexport function requireRunnerSessionContext(request: RequestWithContext): RunnerSessionContext {\n const context = getRunnerSessionContext(request);\n if (!context) {\n throw new Error('Runner session context is not available on this request');\n }\n return context;\n}\n"],"names":["ClientError","AUTH_USER","AUTH_RUNNER_REGISTRATION_TOKEN","AUTH_RUNNER_SESSION","AUTH_LEASED_JOB","AUTH_PROVISIONER_TOKEN","buildUserContext","params","memberships","userId","email","name","canAccess","workspaceId","some","m","hasRole","role","USER_CONTEXT_KEY","Symbol","for","LEASED_JOB_CONTEXT_KEY","PROVISIONER_CONTEXT_KEY","RUNNER_SESSION_CONTEXT_KEY","setUserContext","request","context","getUserContext","requireUserContext","Error","requireWorkspaceAccess","status","membership","find","setProvisionerContext","getProvisionerContext","requireProvisionerContext","setLeasedJobContext","getLeasedJobContext","requireLeasedJobContext","setRunnerSessionContext","getRunnerSessionContext","requireRunnerSessionContext"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nimport type {WorkspaceRole} from '@shipfox/api-workspaces-dto';\nimport {ClientError} from '@shipfox/node-fastify';\n\nexport const AUTH_USER = 'user';\nexport const AUTH_RUNNER_REGISTRATION_TOKEN = 'runner-registration-token';\nexport const AUTH_RUNNER_SESSION = 'runner-session';\nexport const AUTH_RUNNER_CONTROL_SESSION = 'runner-control-session';\nexport const AUTH_LEASED_JOB = 'leased-job';\nexport const AUTH_PROVISIONER_TOKEN = 'provisioner-token';\n\nexport type WorkspaceStatus = 'active' | 'suspended' | 'deleted';\n\nexport interface UserContextMembership {\n workspaceId: string;\n role: WorkspaceRole;\n}\n\nexport interface UserContext {\n userId: string;\n email: string;\n name: string | null;\n memberships: ReadonlyArray<UserContextMembership>;\n canAccess(workspaceId: string): boolean;\n hasRole(workspaceId: string, role: WorkspaceRole): boolean;\n}\n\nexport interface BuildUserContextParams {\n userId: string;\n email: string;\n name?: string | null | undefined;\n memberships?: ReadonlyArray<UserContextMembership> | undefined;\n}\n\nexport function buildUserContext(params: BuildUserContextParams): UserContext {\n const memberships = params.memberships ?? [];\n return {\n userId: params.userId,\n email: params.email,\n name: params.name ?? null,\n memberships,\n canAccess: (workspaceId) => memberships.some((m) => m.workspaceId === workspaceId),\n hasRole: (workspaceId, role) =>\n memberships.some((m) => m.workspaceId === workspaceId && m.role === role),\n };\n}\n\nexport type ProvisionerContext =\n | {provisionerTokenId: string; scope: 'installation'}\n | {provisionerTokenId: string; scope: 'workspace'; workspaceId: string};\n\nexport type LeasedJobContext = JobLeaseTokenClaims;\nexport type RunnerSessionContext = RunnerSessionTokenClaims;\nexport interface RunnerControlSessionContext {\n runnerControlSessionId: string;\n runnerInstanceId: string;\n provisionerId: string;\n}\n\ntype RequestWithContext = object;\n\nconst USER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/user');\nconst LEASED_JOB_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/leased-job');\nconst PROVISIONER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/provisioner');\nconst RUNNER_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/runner-session');\nconst RUNNER_CONTROL_SESSION_CONTEXT_KEY = Symbol.for(\n '@shipfox/api-auth-context/runner-control-session',\n);\n\nexport function setUserContext(request: RequestWithContext, context: UserContext): void {\n (request as Record<symbol, unknown>)[USER_CONTEXT_KEY] = context;\n}\n\nexport function getUserContext(request: RequestWithContext): UserContext | null {\n return (\n ((request as Record<symbol, unknown>)[USER_CONTEXT_KEY] as UserContext | undefined) ?? null\n );\n}\n\nexport function requireUserContext(request: RequestWithContext): UserContext {\n const context = getUserContext(request);\n if (!context) {\n throw new Error('User context is not available on this request');\n }\n return context;\n}\n\nexport interface RequireWorkspaceAccessParams {\n request: RequestWithContext;\n workspaceId: string;\n}\n\nexport interface RequireWorkspaceAccessResult {\n workspaceId: string;\n userId: string;\n role: WorkspaceRole;\n}\n\n/**\n * Authorizes the request's user for a workspace using only the verified session\n * token. Membership and role come from the token claims, so the check stays\n * stateless and does not read the database on the request path.\n */\nexport function requireWorkspaceAccess(\n params: RequireWorkspaceAccessParams,\n): RequireWorkspaceAccessResult {\n const context = getUserContext(params.request);\n if (!context) {\n throw new ClientError('Authentication required', 'unauthorized', {status: 401});\n }\n\n const membership = context.memberships.find((m) => m.workspaceId === params.workspaceId);\n if (!membership) {\n throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});\n }\n\n return {workspaceId: params.workspaceId, userId: context.userId, role: membership.role};\n}\n\nexport function setProvisionerContext(\n request: RequestWithContext,\n context: ProvisionerContext,\n): void {\n (request as Record<symbol, unknown>)[PROVISIONER_CONTEXT_KEY] = context;\n}\n\nexport function getProvisionerContext(request: RequestWithContext): ProvisionerContext | null {\n return (\n ((request as Record<symbol, unknown>)[PROVISIONER_CONTEXT_KEY] as\n | ProvisionerContext\n | undefined) ?? null\n );\n}\n\nexport function requireProvisionerContext(request: RequestWithContext): ProvisionerContext {\n const context = getProvisionerContext(request);\n if (!context) {\n throw new Error('Provisioner context is not available on this request');\n }\n return context;\n}\n\nexport function requireWorkspaceProvisionerContext(\n request: RequestWithContext,\n): Extract<ProvisionerContext, {scope: 'workspace'}> {\n const context = requireProvisionerContext(request);\n if (context.scope !== 'workspace') {\n throw new ClientError('Workspace provisioner credential required', 'forbidden', {status: 403});\n }\n return context;\n}\n\nexport function requireInstallationProvisionerContext(\n request: RequestWithContext,\n): Extract<ProvisionerContext, {scope: 'installation'}> {\n const context = requireProvisionerContext(request);\n if (context.scope !== 'installation') {\n throw new ClientError('Installation provisioner credential required', 'forbidden', {\n status: 403,\n });\n }\n return context;\n}\n\nexport function setLeasedJobContext(request: RequestWithContext, context: LeasedJobContext): void {\n (request as Record<symbol, unknown>)[LEASED_JOB_CONTEXT_KEY] = context;\n}\n\nexport function getLeasedJobContext(request: RequestWithContext): LeasedJobContext | null {\n return (\n ((request as Record<symbol, unknown>)[LEASED_JOB_CONTEXT_KEY] as\n | LeasedJobContext\n | undefined) ?? null\n );\n}\n\nexport function requireLeasedJobContext(request: RequestWithContext): LeasedJobContext {\n const context = getLeasedJobContext(request);\n if (!context) {\n throw new Error('Leased job context is not available on this request');\n }\n return context;\n}\n\nexport function setRunnerSessionContext(\n request: RequestWithContext,\n context: RunnerSessionContext,\n): void {\n (request as Record<symbol, unknown>)[RUNNER_SESSION_CONTEXT_KEY] = context;\n}\n\nexport function getRunnerSessionContext(request: RequestWithContext): RunnerSessionContext | null {\n return (\n ((request as Record<symbol, unknown>)[RUNNER_SESSION_CONTEXT_KEY] as\n | RunnerSessionContext\n | undefined) ?? null\n );\n}\n\nexport function requireRunnerSessionContext(request: RequestWithContext): RunnerSessionContext {\n const context = getRunnerSessionContext(request);\n if (!context) {\n throw new Error('Runner session context is not available on this request');\n }\n return context;\n}\n\nexport function setRunnerControlSessionContext(\n request: RequestWithContext,\n context: RunnerControlSessionContext,\n): void {\n (request as Record<symbol, unknown>)[RUNNER_CONTROL_SESSION_CONTEXT_KEY] = context;\n}\n\nexport function requireRunnerControlSessionContext(\n request: RequestWithContext,\n): RunnerControlSessionContext {\n const context = (request as Record<symbol, unknown>)[RUNNER_CONTROL_SESSION_CONTEXT_KEY] as\n | RunnerControlSessionContext\n | undefined;\n if (!context) throw new Error('Runner control session context is not available on this request');\n return context;\n}\n"],"names":["ClientError","AUTH_USER","AUTH_RUNNER_REGISTRATION_TOKEN","AUTH_RUNNER_SESSION","AUTH_RUNNER_CONTROL_SESSION","AUTH_LEASED_JOB","AUTH_PROVISIONER_TOKEN","buildUserContext","params","memberships","userId","email","name","canAccess","workspaceId","some","m","hasRole","role","USER_CONTEXT_KEY","Symbol","for","LEASED_JOB_CONTEXT_KEY","PROVISIONER_CONTEXT_KEY","RUNNER_SESSION_CONTEXT_KEY","RUNNER_CONTROL_SESSION_CONTEXT_KEY","setUserContext","request","context","getUserContext","requireUserContext","Error","requireWorkspaceAccess","status","membership","find","setProvisionerContext","getProvisionerContext","requireProvisionerContext","requireWorkspaceProvisionerContext","scope","requireInstallationProvisionerContext","setLeasedJobContext","getLeasedJobContext","requireLeasedJobContext","setRunnerSessionContext","getRunnerSessionContext","requireRunnerSessionContext","setRunnerControlSessionContext","requireRunnerControlSessionContext"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-auth-context",
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",
@@ -18,9 +18,9 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "@shipfox/api-auth-dto": "5.0.0",
22
- "@shipfox/api-workspaces-dto": "5.0.0",
23
- "@shipfox/node-fastify": "0.2.3"
21
+ "@shipfox/api-auth-dto": "6.0.0",
22
+ "@shipfox/api-workspaces-dto": "6.0.0",
23
+ "@shipfox/node-fastify": "0.2.4"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@shipfox/biome": "1.8.2",
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import {ClientError} from '@shipfox/node-fastify';
5
5
  export const AUTH_USER = 'user';
6
6
  export const AUTH_RUNNER_REGISTRATION_TOKEN = 'runner-registration-token';
7
7
  export const AUTH_RUNNER_SESSION = 'runner-session';
8
+ export const AUTH_RUNNER_CONTROL_SESSION = 'runner-control-session';
8
9
  export const AUTH_LEASED_JOB = 'leased-job';
9
10
  export const AUTH_PROVISIONER_TOKEN = 'provisioner-token';
10
11
 
@@ -44,13 +45,17 @@ export function buildUserContext(params: BuildUserContextParams): UserContext {
44
45
  };
45
46
  }
46
47
 
47
- export interface ProvisionerContext {
48
- provisionerTokenId: string;
49
- workspaceId: string;
50
- }
48
+ export type ProvisionerContext =
49
+ | {provisionerTokenId: string; scope: 'installation'}
50
+ | {provisionerTokenId: string; scope: 'workspace'; workspaceId: string};
51
51
 
52
52
  export type LeasedJobContext = JobLeaseTokenClaims;
53
53
  export type RunnerSessionContext = RunnerSessionTokenClaims;
54
+ export interface RunnerControlSessionContext {
55
+ runnerControlSessionId: string;
56
+ runnerInstanceId: string;
57
+ provisionerId: string;
58
+ }
54
59
 
55
60
  type RequestWithContext = object;
56
61
 
@@ -58,6 +63,9 @@ const USER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/user');
58
63
  const LEASED_JOB_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/leased-job');
59
64
  const PROVISIONER_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/provisioner');
60
65
  const RUNNER_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth-context/runner-session');
66
+ const RUNNER_CONTROL_SESSION_CONTEXT_KEY = Symbol.for(
67
+ '@shipfox/api-auth-context/runner-control-session',
68
+ );
61
69
 
62
70
  export function setUserContext(request: RequestWithContext, context: UserContext): void {
63
71
  (request as Record<symbol, unknown>)[USER_CONTEXT_KEY] = context;
@@ -132,6 +140,28 @@ export function requireProvisionerContext(request: RequestWithContext): Provisio
132
140
  return context;
133
141
  }
134
142
 
143
+ export function requireWorkspaceProvisionerContext(
144
+ request: RequestWithContext,
145
+ ): Extract<ProvisionerContext, {scope: 'workspace'}> {
146
+ const context = requireProvisionerContext(request);
147
+ if (context.scope !== 'workspace') {
148
+ throw new ClientError('Workspace provisioner credential required', 'forbidden', {status: 403});
149
+ }
150
+ return context;
151
+ }
152
+
153
+ export function requireInstallationProvisionerContext(
154
+ request: RequestWithContext,
155
+ ): Extract<ProvisionerContext, {scope: 'installation'}> {
156
+ const context = requireProvisionerContext(request);
157
+ if (context.scope !== 'installation') {
158
+ throw new ClientError('Installation provisioner credential required', 'forbidden', {
159
+ status: 403,
160
+ });
161
+ }
162
+ return context;
163
+ }
164
+
135
165
  export function setLeasedJobContext(request: RequestWithContext, context: LeasedJobContext): void {
136
166
  (request as Record<symbol, unknown>)[LEASED_JOB_CONTEXT_KEY] = context;
137
167
  }
@@ -174,3 +204,20 @@ export function requireRunnerSessionContext(request: RequestWithContext): Runner
174
204
  }
175
205
  return context;
176
206
  }
207
+
208
+ export function setRunnerControlSessionContext(
209
+ request: RequestWithContext,
210
+ context: RunnerControlSessionContext,
211
+ ): void {
212
+ (request as Record<symbol, unknown>)[RUNNER_CONTROL_SESSION_CONTEXT_KEY] = context;
213
+ }
214
+
215
+ export function requireRunnerControlSessionContext(
216
+ request: RequestWithContext,
217
+ ): RunnerControlSessionContext {
218
+ const context = (request as Record<symbol, unknown>)[RUNNER_CONTROL_SESSION_CONTEXT_KEY] as
219
+ | RunnerControlSessionContext
220
+ | undefined;
221
+ if (!context) throw new Error('Runner control session context is not available on this request');
222
+ return context;
223
+ }
@@ -0,0 +1,44 @@
1
+ import {ClientError} from '@shipfox/node-fastify';
2
+ import {
3
+ requireInstallationProvisionerContext,
4
+ requireWorkspaceProvisionerContext,
5
+ setProvisionerContext,
6
+ } from './index.js';
7
+
8
+ describe('provisioner context', () => {
9
+ it('returns only a workspace context from the workspace guard', () => {
10
+ const request = {};
11
+ const context = {
12
+ provisionerTokenId: crypto.randomUUID(),
13
+ scope: 'workspace' as const,
14
+ workspaceId: crypto.randomUUID(),
15
+ };
16
+ setProvisionerContext(request, context);
17
+
18
+ const result = requireWorkspaceProvisionerContext(request);
19
+
20
+ expect(result).toEqual(context);
21
+ });
22
+
23
+ it('returns only an installation context from the installation guard', () => {
24
+ const request = {};
25
+ const context = {provisionerTokenId: crypto.randomUUID(), scope: 'installation' as const};
26
+ setProvisionerContext(request, context);
27
+
28
+ const result = requireInstallationProvisionerContext(request);
29
+
30
+ expect(result).toEqual(context);
31
+ });
32
+
33
+ it('rejects a credential with the wrong scope', () => {
34
+ const request = {};
35
+ setProvisionerContext(request, {
36
+ provisionerTokenId: crypto.randomUUID(),
37
+ scope: 'installation',
38
+ });
39
+
40
+ expect(() => requireWorkspaceProvisionerContext(request)).toThrow(
41
+ new ClientError('Workspace provisioner credential required', 'forbidden', {status: 403}),
42
+ );
43
+ });
44
+ });