@smithers-orchestrator/gateway 0.21.0 → 0.22.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/openapi.yaml CHANGED
@@ -2197,8 +2197,6 @@ paths:
2197
2197
  type: string
2198
2198
  -
2199
2199
  type: number
2200
- -
2201
- type: integer
2202
2200
  -
2203
2201
  type: boolean
2204
2202
  -
@@ -2623,8 +2621,6 @@ paths:
2623
2621
  type: string
2624
2622
  -
2625
2623
  type: number
2626
- -
2627
- type: integer
2628
2624
  -
2629
2625
  type: boolean
2630
2626
  -
@@ -6842,7 +6838,7 @@ paths:
6842
6838
  - InvalidRequest
6843
6839
  - Unauthorized
6844
6840
  - Forbidden
6845
- - RunNotFound
6841
+ - CronNotFound
6846
6842
  - Internal
6847
6843
  message:
6848
6844
  type: string
@@ -6897,7 +6893,7 @@ paths:
6897
6893
  - InvalidRequest
6898
6894
  - Unauthorized
6899
6895
  - Forbidden
6900
- - RunNotFound
6896
+ - CronNotFound
6901
6897
  - Internal
6902
6898
  message:
6903
6899
  type: string
@@ -6952,7 +6948,7 @@ paths:
6952
6948
  - InvalidRequest
6953
6949
  - Unauthorized
6954
6950
  - Forbidden
6955
- - RunNotFound
6951
+ - CronNotFound
6956
6952
  - Internal
6957
6953
  message:
6958
6954
  type: string
@@ -6970,7 +6966,7 @@ paths:
6970
6966
  refresh:
6971
6967
  type: string
6972
6968
  404:
6973
- description: "The run does not exist."
6969
+ description: "The cron schedule does not exist."
6974
6970
  content:
6975
6971
  application/json:
6976
6972
  schema:
@@ -7007,7 +7003,7 @@ paths:
7007
7003
  - InvalidRequest
7008
7004
  - Unauthorized
7009
7005
  - Forbidden
7010
- - RunNotFound
7006
+ - CronNotFound
7011
7007
  - Internal
7012
7008
  message:
7013
7009
  type: string
@@ -7062,7 +7058,7 @@ paths:
7062
7058
  - InvalidRequest
7063
7059
  - Unauthorized
7064
7060
  - Forbidden
7065
- - RunNotFound
7061
+ - CronNotFound
7066
7062
  - Internal
7067
7063
  message:
7068
7064
  type: string
@@ -7224,7 +7220,7 @@ paths:
7224
7220
  - InvalidInput
7225
7221
  - Unauthorized
7226
7222
  - Forbidden
7227
- - RunNotFound
7223
+ - CronNotFound
7228
7224
  - Internal
7229
7225
  message:
7230
7226
  type: string
@@ -7280,7 +7276,7 @@ paths:
7280
7276
  - InvalidInput
7281
7277
  - Unauthorized
7282
7278
  - Forbidden
7283
- - RunNotFound
7279
+ - CronNotFound
7284
7280
  - Internal
7285
7281
  message:
7286
7282
  type: string
@@ -7336,7 +7332,7 @@ paths:
7336
7332
  - InvalidInput
7337
7333
  - Unauthorized
7338
7334
  - Forbidden
7339
- - RunNotFound
7335
+ - CronNotFound
7340
7336
  - Internal
7341
7337
  message:
7342
7338
  type: string
@@ -7354,7 +7350,7 @@ paths:
7354
7350
  refresh:
7355
7351
  type: string
7356
7352
  404:
7357
- description: "The run does not exist."
7353
+ description: "The cron schedule does not exist."
7358
7354
  content:
7359
7355
  application/json:
7360
7356
  schema:
@@ -7392,7 +7388,7 @@ paths:
7392
7388
  - InvalidInput
7393
7389
  - Unauthorized
7394
7390
  - Forbidden
7395
- - RunNotFound
7391
+ - CronNotFound
7396
7392
  - Internal
7397
7393
  message:
7398
7394
  type: string
@@ -7448,7 +7444,7 @@ paths:
7448
7444
  - InvalidInput
7449
7445
  - Unauthorized
7450
7446
  - Forbidden
7451
- - RunNotFound
7447
+ - CronNotFound
7452
7448
  - Internal
7453
7449
  message:
7454
7450
  type: string
@@ -7493,6 +7489,7 @@ components:
7493
7489
  - Unauthorized
7494
7490
  - Forbidden
7495
7491
  - RunNotFound
7492
+ - CronNotFound
7496
7493
  - NodeNotFound
7497
7494
  - IterationNotFound
7498
7495
  - NodeHasNoOutput
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithers-orchestrator/gateway",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Stable Smithers Gateway RPC contracts, auth scopes, and deployment metadata",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,3 +1,5 @@
1
+ import { getRequiredScopeForGatewayMethod } from "../rpc/index.ts";
2
+
1
3
  export const GATEWAY_SCOPE_VALUES = [
2
4
  "run:read",
3
5
  "run:write",
@@ -61,6 +63,22 @@ function legacyAccessImplies(scope: string, required: GatewayScope): boolean {
61
63
  }
62
64
  }
63
65
 
66
+ /**
67
+ * A name grant ("getRun") or wildcard-prefix grant ("runs.*") authorizes a
68
+ * method invocation, but it must never escalate beyond the scope that method
69
+ * itself requires: holding "getRun" lets you call getRun (a run:read method),
70
+ * not a run:admin method that happens to share the matched name/prefix. We gate
71
+ * the grant on the dispatched method's own required scope so a name/prefix grant
72
+ * can confer at most what that method legitimately needs.
73
+ */
74
+ function methodGrantSatisfiesRequiredScope(methodName: string, requiredScope: GatewayScope): boolean {
75
+ const methodScope = getRequiredScopeForGatewayMethod(methodName);
76
+ if (methodScope === undefined) {
77
+ return false;
78
+ }
79
+ return gatewayScopeImplies(methodScope, requiredScope);
80
+ }
81
+
64
82
  export function hasGatewayScope(
65
83
  grantedScopes: readonly string[],
66
84
  requiredScope: GatewayScope,
@@ -71,10 +89,15 @@ export function hasGatewayScope(
71
89
  return true;
72
90
  }
73
91
  for (const granted of normalized) {
74
- if (methodName && granted === methodName) {
92
+ if (methodName && granted === methodName && methodGrantSatisfiesRequiredScope(methodName, requiredScope)) {
75
93
  return true;
76
94
  }
77
- if (methodName && granted.endsWith(".*") && methodName.startsWith(granted.slice(0, -1))) {
95
+ if (
96
+ methodName &&
97
+ granted.endsWith(".*") &&
98
+ methodName.startsWith(granted.slice(0, -1)) &&
99
+ methodGrantSatisfiesRequiredScope(methodName, requiredScope)
100
+ ) {
78
101
  return true;
79
102
  }
80
103
  if (isGatewayScope(granted) && gatewayScopeImplies(granted, requiredScope)) {
package/src/rpc/index.ts CHANGED
@@ -30,6 +30,7 @@ export type GatewayRpcErrorCode =
30
30
  | "Unauthorized"
31
31
  | "Forbidden"
32
32
  | "RunNotFound"
33
+ | "CronNotFound"
33
34
  | "NodeNotFound"
34
35
  | "IterationNotFound"
35
36
  | "NodeHasNoOutput"
@@ -288,6 +289,10 @@ const arraySchema = (items: JsonSchema, description: string): JsonSchema => ({
288
289
  });
289
290
 
290
291
  export const anyJsonSchema: JsonSchema = {
292
+ // The branches are mutually exclusive so a value matches exactly one of them
293
+ // under strict `oneOf` semantics. The `number` branch already covers integers
294
+ // (an integer is a JSON number), so a separate `integer` branch would make
295
+ // every integer match two branches and fail `oneOf` validation.
291
296
  description: "Any JSON value.",
292
297
  nullable: true,
293
298
  oneOf: [
@@ -295,7 +300,6 @@ export const anyJsonSchema: JsonSchema = {
295
300
  { type: "array", items: { nullable: true } },
296
301
  { type: "string" },
297
302
  { type: "number" },
298
- { type: "integer" },
299
303
  { type: "boolean" },
300
304
  { type: "null" },
301
305
  ],
@@ -324,6 +328,7 @@ export const GATEWAY_RPC_ERRORS: Record<GatewayRpcErrorCode, GatewayRpcErrorDefi
324
328
  Unauthorized: { version: SMITHERS_API_VERSION, code: "Unauthorized", httpStatus: 401, description: "Authentication failed or the token expired." },
325
329
  Forbidden: { version: SMITHERS_API_VERSION, code: "Forbidden", httpStatus: 403, description: "The token is missing the required scope." },
326
330
  RunNotFound: { version: SMITHERS_API_VERSION, code: "RunNotFound", httpStatus: 404, description: "The run does not exist." },
331
+ CronNotFound: { version: SMITHERS_API_VERSION, code: "CronNotFound", httpStatus: 404, description: "The cron schedule does not exist." },
327
332
  NodeNotFound: { version: SMITHERS_API_VERSION, code: "NodeNotFound", httpStatus: 404, description: "The node does not exist on the run." },
328
333
  IterationNotFound: { version: SMITHERS_API_VERSION, code: "IterationNotFound", httpStatus: 404, description: "The requested node iteration does not exist." },
329
334
  NodeHasNoOutput: { version: SMITHERS_API_VERSION, code: "NodeHasNoOutput", httpStatus: 404, description: "The node has not produced output." },
@@ -637,7 +642,7 @@ export const GATEWAY_RPC_DEFINITIONS: readonly GatewayRpcDefinition[] = [
637
642
  requiredScope: "cron:write",
638
643
  requestSchema: objectSchema({ cronId: stringSchema("Cron id.") }, ["cronId"]),
639
644
  responseSchema: objectSchema({ cronId: stringSchema("Cron id."), removed: booleanSchema("True when removed.") }, ["cronId", "removed"]),
640
- errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
645
+ errors: ["InvalidRequest", "Unauthorized", "Forbidden", "CronNotFound", "Internal"],
641
646
  exampleRequest: { cronId: "cron_01" },
642
647
  exampleResponse: { cronId: "cron_01", removed: true },
643
648
  },
@@ -651,7 +656,7 @@ export const GATEWAY_RPC_DEFINITIONS: readonly GatewayRpcDefinition[] = [
651
656
  requiredScope: "cron:write",
652
657
  requestSchema: objectSchema({ cronId: stringSchema("Cron id."), workflow, input: objectSchema({}, [], "Workflow input.", true) }),
653
658
  responseSchema: objectSchema({ runId, workflow }, ["runId", "workflow"]),
654
- errors: ["InvalidRequest", "InvalidInput", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
659
+ errors: ["InvalidRequest", "InvalidInput", "Unauthorized", "Forbidden", "CronNotFound", "Internal"],
655
660
  exampleRequest: { cronId: "cron_01", input: { dryRun: true } },
656
661
  exampleResponse: { runId: "run_02", workflow: "deploy" },
657
662
  },