@code-partner/codepipe-hub 0.14.1-dev.422.gf90f06c4 → 0.14.1-dev.424.ge3c8cc77
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/hub/dist/embedded.js +66 -9
- package/hub/dist/index.js +513 -178
- package/package.json +1 -1
package/hub/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var PLATFORM_VERSION;
|
|
|
13
13
|
var init_version_generated = __esm({
|
|
14
14
|
"../packages/shared/dist/version.generated.js"() {
|
|
15
15
|
"use strict";
|
|
16
|
-
PLATFORM_VERSION = "0.14.1-dev.
|
|
16
|
+
PLATFORM_VERSION = "0.14.1-dev.424.ge3c8cc77";
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -31,8 +31,8 @@ var init_version = __esm({
|
|
|
31
31
|
"../packages/shared/dist/version.js"() {
|
|
32
32
|
"use strict";
|
|
33
33
|
init_version_generated();
|
|
34
|
-
PROTOCOL_VERSION =
|
|
35
|
-
MIN_SUPPORTED_PROTOCOL =
|
|
34
|
+
PROTOCOL_VERSION = 5;
|
|
35
|
+
MIN_SUPPORTED_PROTOCOL = 5;
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -2960,21 +2960,14 @@ var init_duration = __esm({
|
|
|
2960
2960
|
});
|
|
2961
2961
|
|
|
2962
2962
|
// ../packages/shared/dist/node-handshake.js
|
|
2963
|
-
function
|
|
2964
|
-
const found = [];
|
|
2963
|
+
function readNodeSocketSecret(headers) {
|
|
2965
2964
|
const raw = headers["authorization"];
|
|
2966
2965
|
const header = Array.isArray(raw) ? raw[0] : raw;
|
|
2967
|
-
if (typeof header
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
}
|
|
2973
|
-
const fromQuery = query?.token;
|
|
2974
|
-
if (typeof fromQuery === "string" && fromQuery !== "") {
|
|
2975
|
-
found.push({ token: fromQuery, source: "query" });
|
|
2976
|
-
}
|
|
2977
|
-
return found;
|
|
2966
|
+
if (typeof header !== "string")
|
|
2967
|
+
return null;
|
|
2968
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
2969
|
+
const token = match?.[1]?.trim();
|
|
2970
|
+
return token !== void 0 && token !== "" ? token : null;
|
|
2978
2971
|
}
|
|
2979
2972
|
function decideNodeHandshake(frame, hub, recognizedCapabilities, authenticatedAs) {
|
|
2980
2973
|
const handshake = parseNodeHandshake(frame);
|
|
@@ -8682,7 +8675,10 @@ var init_route_definition = __esm({
|
|
|
8682
8675
|
node: ["/node/v1"],
|
|
8683
8676
|
local: ["/local"],
|
|
8684
8677
|
mcp: ["/mcp"],
|
|
8685
|
-
bootstrap:
|
|
8678
|
+
// `/cli` is the CLI's own bootstrap: the connection request it opens, the
|
|
8679
|
+
// page and the email link a person confirms it on (DEV-927 settled it here
|
|
8680
|
+
// rather than moving a path the CLI and a sent email already hold).
|
|
8681
|
+
bootstrap: ["/auth", "/bootstrap", "/cli"],
|
|
8686
8682
|
operational: ["/healthz", "/readyz"],
|
|
8687
8683
|
// Static serves the browser bundle from the root and the install script from
|
|
8688
8684
|
// its documented path (ADR-0060 §5), so it has no single prefix to check.
|
|
@@ -8704,7 +8700,8 @@ var init_route_definition = __esm({
|
|
|
8704
8700
|
"/api/tasks",
|
|
8705
8701
|
"/api/projects",
|
|
8706
8702
|
"/api/admin",
|
|
8707
|
-
"/api/notifications"
|
|
8703
|
+
"/api/notifications",
|
|
8704
|
+
"/bug-report"
|
|
8708
8705
|
];
|
|
8709
8706
|
PUBLIC_MUTATION_ALLOWLIST = [
|
|
8710
8707
|
"auth.login",
|
|
@@ -8712,18 +8709,20 @@ var init_route_definition = __esm({
|
|
|
8712
8709
|
"auth.loginPassword",
|
|
8713
8710
|
"auth.logout",
|
|
8714
8711
|
"auth.register",
|
|
8715
|
-
"auth.setPassword",
|
|
8716
8712
|
"bootstrap.request",
|
|
8717
|
-
"bootstrap.confirm",
|
|
8718
8713
|
"bootstrap.authorizeEmail",
|
|
8719
|
-
|
|
8714
|
+
// DEV-927: a node registers with a registration token in the body — the
|
|
8715
|
+
// token IS the credential, and there is no node identity yet to bear it.
|
|
8716
|
+
"node.sandbox.register",
|
|
8717
|
+
"node.farm.register",
|
|
8718
|
+
"node.runner.register"
|
|
8720
8719
|
];
|
|
8721
8720
|
}
|
|
8722
8721
|
});
|
|
8723
8722
|
|
|
8724
8723
|
// src/http/route-registry.ts
|
|
8725
|
-
function installRouteGuard(app, mode, log) {
|
|
8726
|
-
const registry = new RouteRegistry(mode, log);
|
|
8724
|
+
function installRouteGuard(app, mode, log, legacy = []) {
|
|
8725
|
+
const registry = new RouteRegistry(mode, log, legacy);
|
|
8727
8726
|
app.addHook("onRoute", (route) => registry.inspect(route));
|
|
8728
8727
|
return registry;
|
|
8729
8728
|
}
|
|
@@ -8744,15 +8743,18 @@ var init_route_registry = __esm({
|
|
|
8744
8743
|
init_route_definition();
|
|
8745
8744
|
META_KEY = "codepipeRoute";
|
|
8746
8745
|
RouteRegistry = class {
|
|
8747
|
-
constructor(mode, log = { warn: (message) => console.warn(message) }) {
|
|
8746
|
+
constructor(mode, log = { warn: (message) => console.warn(message) }, legacy = []) {
|
|
8748
8747
|
this.mode = mode;
|
|
8749
8748
|
this.log = log;
|
|
8749
|
+
this.legacyAllowed = new Set(legacy);
|
|
8750
8750
|
}
|
|
8751
8751
|
routes = /* @__PURE__ */ new Map();
|
|
8752
8752
|
paths = /* @__PURE__ */ new Set();
|
|
8753
8753
|
operationIds = /* @__PURE__ */ new Set();
|
|
8754
8754
|
unclassified = 0;
|
|
8755
8755
|
invalid = 0;
|
|
8756
|
+
legacyAllowed;
|
|
8757
|
+
legacySeen = /* @__PURE__ */ new Set();
|
|
8756
8758
|
/** Every route registered with metadata, in registration order. */
|
|
8757
8759
|
list() {
|
|
8758
8760
|
return [...this.routes.values()];
|
|
@@ -8770,6 +8772,12 @@ var init_route_registry = __esm({
|
|
|
8770
8772
|
unclassifiedCount() {
|
|
8771
8773
|
return this.unclassified;
|
|
8772
8774
|
}
|
|
8775
|
+
/** The same registrations by name (`METHOD /path`), so a test can hold the
|
|
8776
|
+
* legacy list to exactly what the process still serves: an entry nobody
|
|
8777
|
+
* registers any more is a stale allowance, and it fails there. */
|
|
8778
|
+
unclassifiedRoutes() {
|
|
8779
|
+
return [...this.legacySeen];
|
|
8780
|
+
}
|
|
8773
8781
|
/** Registrations that had metadata and broke a rule. Counted separately from
|
|
8774
8782
|
* the unclassified ones: "not migrated yet" and "migrated wrongly" are
|
|
8775
8783
|
* different problems, and a violation that lands in neither count is a
|
|
@@ -8789,12 +8797,14 @@ var init_route_registry = __esm({
|
|
|
8789
8797
|
if (methods.length === 0) return;
|
|
8790
8798
|
this.paths.add(route.url);
|
|
8791
8799
|
if (!meta) {
|
|
8792
|
-
this.
|
|
8793
|
-
if (this.mode === "enforce") {
|
|
8800
|
+
const tolerated = methods.every((method) => this.legacyAllowed.has(`${method} ${route.url}`));
|
|
8801
|
+
if (this.mode === "enforce" && !tolerated) {
|
|
8794
8802
|
throw new Error(
|
|
8795
|
-
`route registry: ${methods.join("/")} ${route.url} registered without metadata \u2014 every route declares its surface, operationId and policy (ADR-0060 \xA73)`
|
|
8803
|
+
`route registry: ${methods.join("/")} ${route.url} registered without metadata \u2014 every route declares its surface, operationId and policy (ADR-0060 \xA73); the old roots are closed since the cutover (DEV-927)`
|
|
8796
8804
|
);
|
|
8797
8805
|
}
|
|
8806
|
+
this.unclassified += 1;
|
|
8807
|
+
for (const method of methods) this.legacySeen.add(`${method} ${route.url}`);
|
|
8798
8808
|
return;
|
|
8799
8809
|
}
|
|
8800
8810
|
const violations = methods.flatMap(
|
|
@@ -12239,6 +12249,8 @@ function createCommandOperations(deps) {
|
|
|
12239
12249
|
throw errors.unprocessable({ details: { reason: outcome.reason } });
|
|
12240
12250
|
case "not_found":
|
|
12241
12251
|
throw errors.notFound();
|
|
12252
|
+
case "rate_limited":
|
|
12253
|
+
throw errors.rateLimited();
|
|
12242
12254
|
}
|
|
12243
12255
|
}
|
|
12244
12256
|
function accountActorOf(actor) {
|
|
@@ -12828,6 +12840,14 @@ function createCommandOperations(deps) {
|
|
|
12828
12840
|
return { value: run.result, replayed: run.replayed };
|
|
12829
12841
|
});
|
|
12830
12842
|
},
|
|
12843
|
+
async issueWorkerRegistrationToken(actor) {
|
|
12844
|
+
const subjectId = actor.principal.subjectId;
|
|
12845
|
+
return await audited(actor, { operationId: "worker.issueRegistrationToken", aggregateType: "account", aggregateId: subjectId, projectId: null }, async (policy) => {
|
|
12846
|
+
await require2(actor, "account.manage", { type: "account" }, policy);
|
|
12847
|
+
const issued = unwrap(await write2.issueWorkerRegistrationToken({ owner: accountActorOf(actor) }));
|
|
12848
|
+
return { value: { secret: issued.secret, expiresAt: toTimestamp(issued.expiresAt) }, replayed: false };
|
|
12849
|
+
});
|
|
12850
|
+
},
|
|
12831
12851
|
async reportInitStatus(actor, command) {
|
|
12832
12852
|
const { projectId, phase } = command;
|
|
12833
12853
|
return await audited(actor, { operationId: "project.reportInitStatus", aggregateType: "project", aggregateId: projectId, projectId, data: { phase, failed: command.error !== void 0 } }, async (policy) => {
|
|
@@ -13125,8 +13145,8 @@ var init_cutover_preflight = __esm({
|
|
|
13125
13145
|
SECRET_CONSUMERS = [
|
|
13126
13146
|
{ consumer: "agent credential in the job bundle (Claude / Codex)", purpose: "agent_execution", implemented: true, note: "issued per job from the project's auth mode" },
|
|
13127
13147
|
{ consumer: "repository clone tokens in the job bundle (GitHub / GitLab / Bitbucket)", purpose: "repository_read", implemented: true, note: "one grant for each host the job clones" },
|
|
13128
|
-
{ consumer: "Context Repo push of the delivery from the SANDBOX (contextWrite)", purpose: "context_write", implemented:
|
|
13129
|
-
{ consumer: "mirror clone token in the job bundle (mirrorWrite, read use)", purpose: "repository_read", implemented:
|
|
13148
|
+
{ consumer: "Context Repo push of the delivery from the SANDBOX (contextWrite)", purpose: "context_write", implemented: true, note: "the SANDBOX publishes the delivery over the host-local relay and the holder of the materializer role pushes it; no write credential on the SANDBOX" },
|
|
13149
|
+
{ consumer: "mirror clone token in the job bundle (mirrorWrite, read use)", purpose: "repository_read", implemented: true, note: "a read grant under the mirror's host, issued by the holder of the role with the other repository grants" },
|
|
13130
13150
|
{ consumer: "tracker writes (YouTrack) \u2014 CLI-side, never on the SANDBOX", purpose: "tracker_write", implemented: true, note: "nothing to move: the holder of the role already writes" },
|
|
13131
13151
|
{ consumer: "named project secrets in custom-action runs \u2014 CLI-side", purpose: "custom_action", implemented: true, note: "nothing to move until a tool-run executes on the SANDBOX" }
|
|
13132
13152
|
];
|
|
@@ -25805,10 +25825,16 @@ async function deleteSandboxRegistration(db, sandboxId) {
|
|
|
25805
25825
|
async function sandboxRoutes(app, deps) {
|
|
25806
25826
|
const { db, config } = deps;
|
|
25807
25827
|
const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 30 });
|
|
25808
|
-
const regTokenMintByEmail = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
|
|
25809
25828
|
app.post(
|
|
25810
|
-
"/sandbox/register",
|
|
25829
|
+
"/node/v1/sandbox/register",
|
|
25811
25830
|
{
|
|
25831
|
+
...routeMeta({
|
|
25832
|
+
surface: "node",
|
|
25833
|
+
operationId: "node.sandbox.register",
|
|
25834
|
+
authPolicy: "public",
|
|
25835
|
+
stability: "preview",
|
|
25836
|
+
summary: "Register a SANDBOX with a registration token"
|
|
25837
|
+
}),
|
|
25812
25838
|
schema: {
|
|
25813
25839
|
body: {
|
|
25814
25840
|
type: "object",
|
|
@@ -25849,14 +25875,29 @@ async function sandboxRoutes(app, deps) {
|
|
|
25849
25875
|
return { sandboxId: id, token };
|
|
25850
25876
|
}
|
|
25851
25877
|
);
|
|
25852
|
-
app.get("/sandbox/me",
|
|
25878
|
+
app.get("/node/v1/sandbox/me", routeMeta({
|
|
25879
|
+
surface: "node",
|
|
25880
|
+
operationId: "node.sandbox.me",
|
|
25881
|
+
authPolicy: "node_actor",
|
|
25882
|
+
resourcePolicy: "node.connect",
|
|
25883
|
+
stability: "preview",
|
|
25884
|
+
summary: "Is this SANDBOX registration still known"
|
|
25885
|
+
}), async (req, reply) => {
|
|
25853
25886
|
const sandbox = await authenticateSandboxByBearer(db, req.headers.authorization);
|
|
25854
25887
|
if (!sandbox) return reply.status(401).send({ error: "unauthorized" });
|
|
25855
25888
|
return { sandboxId: sandbox.id, name: sandbox.name };
|
|
25856
25889
|
});
|
|
25857
25890
|
app.post(
|
|
25858
|
-
"/sandbox/agent-auth",
|
|
25891
|
+
"/node/v1/sandbox/agent-auth",
|
|
25859
25892
|
{
|
|
25893
|
+
...routeMeta({
|
|
25894
|
+
surface: "node",
|
|
25895
|
+
operationId: "node.sandbox.agentAuth",
|
|
25896
|
+
authPolicy: "node_actor",
|
|
25897
|
+
resourcePolicy: "node.connect",
|
|
25898
|
+
stability: "preview",
|
|
25899
|
+
summary: "Report the host's agent login and ask whether to probe it"
|
|
25900
|
+
}),
|
|
25860
25901
|
schema: {
|
|
25861
25902
|
body: {
|
|
25862
25903
|
type: "object",
|
|
@@ -25892,8 +25933,16 @@ async function sandboxRoutes(app, deps) {
|
|
|
25892
25933
|
}
|
|
25893
25934
|
);
|
|
25894
25935
|
app.post(
|
|
25895
|
-
"/sandbox/agent-auth/result",
|
|
25936
|
+
"/node/v1/sandbox/agent-auth/result",
|
|
25896
25937
|
{
|
|
25938
|
+
...routeMeta({
|
|
25939
|
+
surface: "node",
|
|
25940
|
+
operationId: "node.sandbox.agentAuthResult",
|
|
25941
|
+
authPolicy: "node_actor",
|
|
25942
|
+
resourcePolicy: "node.connect",
|
|
25943
|
+
stability: "preview",
|
|
25944
|
+
summary: "Report the verdict of an agent login probe"
|
|
25945
|
+
}),
|
|
25897
25946
|
schema: {
|
|
25898
25947
|
body: {
|
|
25899
25948
|
type: "object",
|
|
@@ -25943,19 +25992,6 @@ async function sandboxRoutes(app, deps) {
|
|
|
25943
25992
|
app.log.info({ email: session.email, revoked }, "sandbox reg token revoked");
|
|
25944
25993
|
return { revoked };
|
|
25945
25994
|
});
|
|
25946
|
-
app.post("/sandbox/reg-token/cli", async (req, reply) => {
|
|
25947
|
-
const auth = await resolveCliAuth(db, config, req.headers.authorization);
|
|
25948
|
-
if (!auth) {
|
|
25949
|
-
return reply.status(401).send({ error: "bad_cli_token" });
|
|
25950
|
-
}
|
|
25951
|
-
if (!regTokenMintByEmail.take(`email:${auth.email}`)) {
|
|
25952
|
-
app.log.warn({ email: auth.email }, "ephemeral sandbox reg token mint throttled");
|
|
25953
|
-
return reply.status(429).send({ error: "too_many_requests" });
|
|
25954
|
-
}
|
|
25955
|
-
const { secret, expiresAt } = await issueEphemeralSandboxRegToken(db, auth.email);
|
|
25956
|
-
app.log.info({ email: auth.email }, "ephemeral sandbox reg token issued for CLI");
|
|
25957
|
-
return { secret, expiresAt };
|
|
25958
|
-
});
|
|
25959
25995
|
app.delete(
|
|
25960
25996
|
"/sandbox/:sandboxId",
|
|
25961
25997
|
async (req, reply) => {
|
|
@@ -25992,11 +26028,11 @@ var init_sandbox2 = __esm({
|
|
|
25992
26028
|
init_sealedbox();
|
|
25993
26029
|
init_abuse_throttle();
|
|
25994
26030
|
init_auth();
|
|
25995
|
-
init_cli_auth();
|
|
25996
26031
|
init_sandbox_reg_tokens();
|
|
25997
26032
|
init_users();
|
|
25998
26033
|
init_agent_auth_probe();
|
|
25999
26034
|
init_ws();
|
|
26035
|
+
init_route_registry();
|
|
26000
26036
|
}
|
|
26001
26037
|
});
|
|
26002
26038
|
|
|
@@ -26060,8 +26096,15 @@ async function farmRoutes(app, deps) {
|
|
|
26060
26096
|
const { db, config } = deps;
|
|
26061
26097
|
const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 10 });
|
|
26062
26098
|
app.post(
|
|
26063
|
-
"/farm/register",
|
|
26099
|
+
"/node/v1/farm/register",
|
|
26064
26100
|
{
|
|
26101
|
+
...routeMeta({
|
|
26102
|
+
surface: "node",
|
|
26103
|
+
operationId: "node.farm.register",
|
|
26104
|
+
authPolicy: "public",
|
|
26105
|
+
stability: "preview",
|
|
26106
|
+
summary: "Register a FARM with the operator's registration token"
|
|
26107
|
+
}),
|
|
26065
26108
|
schema: {
|
|
26066
26109
|
body: {
|
|
26067
26110
|
type: "object",
|
|
@@ -26140,6 +26183,7 @@ var init_farm2 = __esm({
|
|
|
26140
26183
|
init_sealedbox();
|
|
26141
26184
|
init_abuse_throttle();
|
|
26142
26185
|
init_ws();
|
|
26186
|
+
init_route_registry();
|
|
26143
26187
|
}
|
|
26144
26188
|
});
|
|
26145
26189
|
|
|
@@ -26241,8 +26285,15 @@ async function runnerRoutes(app, deps) {
|
|
|
26241
26285
|
const { db, config } = deps;
|
|
26242
26286
|
const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 10 });
|
|
26243
26287
|
app.post(
|
|
26244
|
-
"/runner/register",
|
|
26288
|
+
"/node/v1/runner/register",
|
|
26245
26289
|
{
|
|
26290
|
+
...routeMeta({
|
|
26291
|
+
surface: "node",
|
|
26292
|
+
operationId: "node.runner.register",
|
|
26293
|
+
authPolicy: "public",
|
|
26294
|
+
stability: "preview",
|
|
26295
|
+
summary: "Register a RUNNER host with a personal registration token"
|
|
26296
|
+
}),
|
|
26246
26297
|
schema: {
|
|
26247
26298
|
body: {
|
|
26248
26299
|
type: "object",
|
|
@@ -26345,6 +26396,7 @@ var init_runner2 = __esm({
|
|
|
26345
26396
|
init_users();
|
|
26346
26397
|
init_runner_reg_tokens();
|
|
26347
26398
|
init_ws();
|
|
26399
|
+
init_route_registry();
|
|
26348
26400
|
}
|
|
26349
26401
|
});
|
|
26350
26402
|
|
|
@@ -27524,23 +27576,11 @@ function installSocketKeepalive(socket, periodMs) {
|
|
|
27524
27576
|
}
|
|
27525
27577
|
async function wsRoutes(app, deps) {
|
|
27526
27578
|
const { db, config } = deps;
|
|
27527
|
-
async function authenticateSocket(req,
|
|
27528
|
-
|
|
27529
|
-
|
|
27530
|
-
|
|
27531
|
-
|
|
27532
|
-
return resolved;
|
|
27533
|
-
}
|
|
27534
|
-
return null;
|
|
27535
|
-
}
|
|
27536
|
-
const warnedComponents = /* @__PURE__ */ new Set();
|
|
27537
|
-
function warnAboutQueryCredential(component, remote) {
|
|
27538
|
-
if (warnedComponents.has(component)) return;
|
|
27539
|
-
warnedComponents.add(component);
|
|
27540
|
-
app.log.warn(
|
|
27541
|
-
{ component, remote },
|
|
27542
|
-
"node socket authenticated from the query string \u2014 the credential lands in proxy logs; update the component to send it in the Authorization header (this is logged once)"
|
|
27543
|
-
);
|
|
27579
|
+
async function authenticateSocket(req, resolve4) {
|
|
27580
|
+
const token = readNodeSocketSecret(req.headers);
|
|
27581
|
+
if (token === null) return null;
|
|
27582
|
+
const resolved = await resolve4(token);
|
|
27583
|
+
return resolved === null || resolved === void 0 ? null : resolved;
|
|
27544
27584
|
}
|
|
27545
27585
|
const cliSocket = async (socket, req) => {
|
|
27546
27586
|
let boundProjectId = null;
|
|
@@ -27877,7 +27917,7 @@ async function wsRoutes(app, deps) {
|
|
|
27877
27917
|
app.log.info({ remote: req.ip }, "CLI socket closed (never registered)");
|
|
27878
27918
|
}
|
|
27879
27919
|
});
|
|
27880
|
-
auth = await authenticateSocket(req,
|
|
27920
|
+
auth = await authenticateSocket(req, (token) => authFromSecret(db, config, token));
|
|
27881
27921
|
if (!auth) {
|
|
27882
27922
|
socket.close(1008, "unauthorized");
|
|
27883
27923
|
return;
|
|
@@ -27895,7 +27935,7 @@ async function wsRoutes(app, deps) {
|
|
|
27895
27935
|
void pump();
|
|
27896
27936
|
};
|
|
27897
27937
|
const sandboxSocket = async (socket, req) => {
|
|
27898
|
-
const sandbox = await authenticateSocket(req,
|
|
27938
|
+
const sandbox = await authenticateSocket(req, (token) => findSandboxByToken(db, token));
|
|
27899
27939
|
if (!sandbox) {
|
|
27900
27940
|
socket.close(1008, "unauthorized");
|
|
27901
27941
|
return;
|
|
@@ -28013,7 +28053,7 @@ async function wsRoutes(app, deps) {
|
|
|
28013
28053
|
});
|
|
28014
28054
|
};
|
|
28015
28055
|
const farmSocket = async (socket, req) => {
|
|
28016
|
-
const farm = await authenticateSocket(req,
|
|
28056
|
+
const farm = await authenticateSocket(req, (token) => findFarmByToken(db, token));
|
|
28017
28057
|
if (!farm) {
|
|
28018
28058
|
socket.close(1008, "unauthorized");
|
|
28019
28059
|
return;
|
|
@@ -28105,7 +28145,7 @@ async function wsRoutes(app, deps) {
|
|
|
28105
28145
|
});
|
|
28106
28146
|
};
|
|
28107
28147
|
const runnerSocket = async (socket, req) => {
|
|
28108
|
-
const runner = await authenticateSocket(req,
|
|
28148
|
+
const runner = await authenticateSocket(req, (token) => findRunnerByToken(db, token));
|
|
28109
28149
|
if (!runner) {
|
|
28110
28150
|
socket.close(1008, "unauthorized");
|
|
28111
28151
|
return;
|
|
@@ -28237,10 +28277,12 @@ async function wsRoutes(app, deps) {
|
|
|
28237
28277
|
}
|
|
28238
28278
|
const auth = parseNodeAuth(parsed);
|
|
28239
28279
|
if (auth !== null) {
|
|
28240
|
-
|
|
28280
|
+
const withFrameCredential = { ...req, headers: { ...req.headers, authorization: `Bearer ${auth.token}` } };
|
|
28281
|
+
if (auth.component !== component || !await authenticate(withFrameCredential)) {
|
|
28241
28282
|
refuse("unauthenticated");
|
|
28242
28283
|
return;
|
|
28243
28284
|
}
|
|
28285
|
+
effectiveReq = withFrameCredential;
|
|
28244
28286
|
authenticated = true;
|
|
28245
28287
|
return;
|
|
28246
28288
|
}
|
|
@@ -28277,7 +28319,7 @@ async function wsRoutes(app, deps) {
|
|
|
28277
28319
|
socket.off("message", listener);
|
|
28278
28320
|
socket.pause();
|
|
28279
28321
|
try {
|
|
28280
|
-
await inner(socket,
|
|
28322
|
+
await inner(socket, effectiveReq);
|
|
28281
28323
|
} catch (err) {
|
|
28282
28324
|
app.log.error({ err, component }, "node socket handler failed after handshake");
|
|
28283
28325
|
socket.close(1011, "handler_failed");
|
|
@@ -28288,6 +28330,7 @@ async function wsRoutes(app, deps) {
|
|
|
28288
28330
|
socket.resume();
|
|
28289
28331
|
};
|
|
28290
28332
|
let authenticated = false;
|
|
28333
|
+
let effectiveReq = req;
|
|
28291
28334
|
const ready = authenticate(req).then((ok2) => {
|
|
28292
28335
|
authenticated = ok2;
|
|
28293
28336
|
});
|
|
@@ -28321,7 +28364,6 @@ async function wsRoutes(app, deps) {
|
|
|
28321
28364
|
});
|
|
28322
28365
|
}
|
|
28323
28366
|
}
|
|
28324
|
-
app.get("/cli/connect", { websocket: true }, cliSocket);
|
|
28325
28367
|
app.get(
|
|
28326
28368
|
"/node/v1/cli/connect",
|
|
28327
28369
|
{
|
|
@@ -28335,9 +28377,8 @@ async function wsRoutes(app, deps) {
|
|
|
28335
28377
|
summary: "Control socket of a CLI instance"
|
|
28336
28378
|
})
|
|
28337
28379
|
},
|
|
28338
|
-
withNodeHandshake("cli", async (r) => await authenticateSocket(r,
|
|
28380
|
+
withNodeHandshake("cli", async (r) => await authenticateSocket(r, (token) => authFromSecret(db, config, token)) !== null, cliSocket)
|
|
28339
28381
|
);
|
|
28340
|
-
app.get("/sandbox/connect", { websocket: true }, sandboxSocket);
|
|
28341
28382
|
app.get(
|
|
28342
28383
|
"/node/v1/sandbox/connect",
|
|
28343
28384
|
{
|
|
@@ -28351,9 +28392,8 @@ async function wsRoutes(app, deps) {
|
|
|
28351
28392
|
summary: "Control socket of a SANDBOX instance"
|
|
28352
28393
|
})
|
|
28353
28394
|
},
|
|
28354
|
-
withNodeHandshake("sandbox", async (r) => await authenticateSocket(r,
|
|
28395
|
+
withNodeHandshake("sandbox", async (r) => await authenticateSocket(r, (token) => findSandboxByToken(db, token)) !== null, sandboxSocket)
|
|
28355
28396
|
);
|
|
28356
|
-
app.get("/farm/connect", { websocket: true }, farmSocket);
|
|
28357
28397
|
app.get(
|
|
28358
28398
|
"/node/v1/farm/connect",
|
|
28359
28399
|
{
|
|
@@ -28367,9 +28407,8 @@ async function wsRoutes(app, deps) {
|
|
|
28367
28407
|
summary: "Control socket of a FARM instance"
|
|
28368
28408
|
})
|
|
28369
28409
|
},
|
|
28370
|
-
withNodeHandshake("farm", async (r) => await authenticateSocket(r,
|
|
28410
|
+
withNodeHandshake("farm", async (r) => await authenticateSocket(r, (token) => findFarmByToken(db, token)) !== null, farmSocket)
|
|
28371
28411
|
);
|
|
28372
|
-
app.get("/runner/connect", { websocket: true }, runnerSocket);
|
|
28373
28412
|
app.get(
|
|
28374
28413
|
"/node/v1/runner/connect",
|
|
28375
28414
|
{
|
|
@@ -28383,7 +28422,7 @@ async function wsRoutes(app, deps) {
|
|
|
28383
28422
|
summary: "Control socket of a RUNNER instance"
|
|
28384
28423
|
})
|
|
28385
28424
|
},
|
|
28386
|
-
withNodeHandshake("runner", async (r) => await authenticateSocket(r,
|
|
28425
|
+
withNodeHandshake("runner", async (r) => await authenticateSocket(r, (token) => findRunnerByToken(db, token)) !== null, runnerSocket)
|
|
28387
28426
|
);
|
|
28388
28427
|
}
|
|
28389
28428
|
var agentLoginProviders, deliveryFollowers, projectCliConnections, projectLastSeenAt, projectLastDuplicateRegisterAt, sandboxConnections, farmConnections, runnerConnections, cliMessageHandlers, cliRegisterHandlers, cliDisconnectHandlers, farmMessageHandlers, runnerMessageHandlers, providerMessageHandlers, RECOGNIZED_NODE_CAPABILITIES, NODE_HANDSHAKE_BACKLOG, NODE_HANDSHAKE_TIMEOUT_MS;
|
|
@@ -28879,9 +28918,7 @@ var DASHBOARD_ROUTES = [
|
|
|
28879
28918
|
var CONSOLE_ROUTES = [/^\/$/];
|
|
28880
28919
|
var DECLARED_RESERVED_ROOTS = [
|
|
28881
28920
|
...Object.values(SURFACE_PREFIXES).flat(),
|
|
28882
|
-
...RETIRED_ROOTS
|
|
28883
|
-
"/cli",
|
|
28884
|
-
"/ws"
|
|
28921
|
+
...RETIRED_ROOTS
|
|
28885
28922
|
];
|
|
28886
28923
|
function isBrowserRoute(app, path) {
|
|
28887
28924
|
const clean = path.split("?")[0].split("#")[0];
|
|
@@ -28978,9 +29015,22 @@ var RETIRED_SOCKET_PATHS = [
|
|
|
28978
29015
|
"/farm/connect",
|
|
28979
29016
|
"/runner/connect"
|
|
28980
29017
|
];
|
|
29018
|
+
var RETIRED_NODE_PATHS = [
|
|
29019
|
+
"/jobs/claim",
|
|
29020
|
+
"/jobs/heartbeat",
|
|
29021
|
+
"/jobs/release",
|
|
29022
|
+
"/jobs/result",
|
|
29023
|
+
"/sandbox/register",
|
|
29024
|
+
"/sandbox/me",
|
|
29025
|
+
"/sandbox/agent-auth",
|
|
29026
|
+
"/sandbox/agent-auth/result",
|
|
29027
|
+
"/sandbox/reg-token/cli",
|
|
29028
|
+
"/farm/register",
|
|
29029
|
+
"/runner/register"
|
|
29030
|
+
];
|
|
28981
29031
|
function isRetiredClientPath(url, registeredPaths = []) {
|
|
28982
29032
|
const path = url.split("?")[0].split("#")[0];
|
|
28983
|
-
if (RETIRED_SOCKET_PATHS.includes(path)) return !registeredPaths.includes(path);
|
|
29033
|
+
if (RETIRED_SOCKET_PATHS.includes(path) || RETIRED_NODE_PATHS.includes(path)) return !registeredPaths.includes(path);
|
|
28984
29034
|
const retired = RETIRED_ROOTS.find((root) => path === root || path.startsWith(`${root}/`));
|
|
28985
29035
|
if (retired === void 0) return false;
|
|
28986
29036
|
return !registeredPaths.some((p) => p === retired || p.startsWith(`${retired}/`));
|
|
@@ -29032,6 +29082,121 @@ async function registerSpaHosts(app, opts) {
|
|
|
29032
29082
|
// src/server.ts
|
|
29033
29083
|
init_route_registry();
|
|
29034
29084
|
|
|
29085
|
+
// src/http/legacy-routes.ts
|
|
29086
|
+
var STATIC_PLUGIN_ROUTES = ["GET /*"];
|
|
29087
|
+
var LEGACY_ROUTES = [
|
|
29088
|
+
"DELETE /api/admin/farms/:farmId",
|
|
29089
|
+
"DELETE /api/admin/runners/:runnerId",
|
|
29090
|
+
"DELETE /api/admin/sandboxes/:sandboxId",
|
|
29091
|
+
"DELETE /projects/:projectId/secrets/:name",
|
|
29092
|
+
"DELETE /runner/:runnerId",
|
|
29093
|
+
"DELETE /runner/reg-token",
|
|
29094
|
+
"DELETE /sandbox/:sandboxId",
|
|
29095
|
+
"DELETE /sandbox/reg-token",
|
|
29096
|
+
"GET /admin/pause",
|
|
29097
|
+
"GET /api/admin/farms",
|
|
29098
|
+
"GET /api/admin/logs",
|
|
29099
|
+
"GET /api/admin/presence",
|
|
29100
|
+
"GET /api/admin/projects",
|
|
29101
|
+
"GET /api/admin/registrations",
|
|
29102
|
+
"GET /api/admin/runners",
|
|
29103
|
+
"GET /api/admin/server",
|
|
29104
|
+
"GET /api/admin/settings",
|
|
29105
|
+
"GET /api/admin/stats",
|
|
29106
|
+
"GET /api/admin/users",
|
|
29107
|
+
"GET /api/notifications/settings",
|
|
29108
|
+
"GET /api/projects/:projectId/tasks/:taskKey/plan-review",
|
|
29109
|
+
"GET /bug-report",
|
|
29110
|
+
"GET /projects/:projectId/actions",
|
|
29111
|
+
"GET /projects/:projectId/adr-backfill",
|
|
29112
|
+
"GET /projects/:projectId/clusters/:anchorKey/journal",
|
|
29113
|
+
"GET /projects/:projectId/config-proposals/:proposalId/agent-activity/stream",
|
|
29114
|
+
"GET /projects/:projectId/context-file",
|
|
29115
|
+
"GET /projects/:projectId/drafts/:draftId/agent-activity/stream",
|
|
29116
|
+
"GET /projects/:projectId/model/drift",
|
|
29117
|
+
"GET /projects/:projectId/model/search",
|
|
29118
|
+
"GET /projects/:projectId/pause",
|
|
29119
|
+
"GET /projects/:projectId/prompts",
|
|
29120
|
+
"GET /projects/:projectId/prompts/:role",
|
|
29121
|
+
"GET /projects/:projectId/review-metrics",
|
|
29122
|
+
"GET /projects/:projectId/runs",
|
|
29123
|
+
"GET /projects/:projectId/runs/:runId/stream",
|
|
29124
|
+
"GET /projects/:projectId/tasks/:taskKey/actions",
|
|
29125
|
+
"GET /projects/:projectId/tasks/:taskKey/agent-activity/stream",
|
|
29126
|
+
"GET /projects/:projectId/tasks/:taskKey/external-review/draft",
|
|
29127
|
+
"GET /projects/:projectId/tasks/:taskKey/review-rounds",
|
|
29128
|
+
"GET /projects/:projectId/tracker-status",
|
|
29129
|
+
"GET /projects/:projectId/tracker/fields",
|
|
29130
|
+
"GET /runner/reg-token",
|
|
29131
|
+
"GET /runners",
|
|
29132
|
+
"GET /sandbox/reg-token",
|
|
29133
|
+
"POST /admin/pause",
|
|
29134
|
+
"POST /api/admin/farms/:farmId/assign",
|
|
29135
|
+
"POST /api/admin/farms/:farmId/unassign",
|
|
29136
|
+
"POST /api/admin/registrations/:id/approve",
|
|
29137
|
+
"POST /api/admin/registrations/:id/reject",
|
|
29138
|
+
"POST /api/admin/runners/:runnerId/owner",
|
|
29139
|
+
"POST /api/admin/users",
|
|
29140
|
+
"POST /api/admin/users/:email/activate",
|
|
29141
|
+
"POST /api/admin/users/:email/block",
|
|
29142
|
+
"POST /api/projects/:projectId/tasks/:taskKey/develop",
|
|
29143
|
+
"POST /api/projects/:projectId/tasks/:taskKey/observer-answer",
|
|
29144
|
+
"POST /api/projects/:projectId/tasks/:taskKey/plan-review",
|
|
29145
|
+
"POST /api/projects/:projectId/tasks/:taskKey/stage",
|
|
29146
|
+
"POST /api/projects/:projectId/tasks/:taskKey/workflow-route",
|
|
29147
|
+
"POST /bug-report",
|
|
29148
|
+
"POST /projects/:projectId/actions/:actionId/run",
|
|
29149
|
+
"POST /projects/:projectId/adr-backfill/discard",
|
|
29150
|
+
"POST /projects/:projectId/adr-backfill/promote",
|
|
29151
|
+
"POST /projects/:projectId/check-readiness",
|
|
29152
|
+
"POST /projects/:projectId/clusters/:anchorKey/reject",
|
|
29153
|
+
"POST /projects/:projectId/creds",
|
|
29154
|
+
"POST /projects/:projectId/detach-from-farm",
|
|
29155
|
+
"POST /projects/:projectId/gh-runner/connect",
|
|
29156
|
+
"POST /projects/:projectId/gh-runner/disconnect",
|
|
29157
|
+
"POST /projects/:projectId/model-bootstrap",
|
|
29158
|
+
"POST /projects/:projectId/move-to-farm",
|
|
29159
|
+
"POST /projects/:projectId/pause",
|
|
29160
|
+
"POST /projects/:projectId/refresh-context-status",
|
|
29161
|
+
"POST /projects/:projectId/retry-init",
|
|
29162
|
+
"POST /projects/:projectId/secrets",
|
|
29163
|
+
"POST /projects/:projectId/sync-config",
|
|
29164
|
+
"POST /projects/:projectId/sync-context",
|
|
29165
|
+
"POST /projects/:projectId/tasks/:taskKey/actions/:actionId/run",
|
|
29166
|
+
"POST /projects/:projectId/tasks/:taskKey/approve",
|
|
29167
|
+
"POST /projects/:projectId/tasks/:taskKey/build-flag",
|
|
29168
|
+
"POST /projects/:projectId/tasks/:taskKey/cancel",
|
|
29169
|
+
"POST /projects/:projectId/tasks/:taskKey/comment",
|
|
29170
|
+
"POST /projects/:projectId/tasks/:taskKey/external-review/close",
|
|
29171
|
+
"POST /projects/:projectId/tasks/:taskKey/external-review/dismiss",
|
|
29172
|
+
"POST /projects/:projectId/tasks/:taskKey/external-review/publish",
|
|
29173
|
+
"POST /projects/:projectId/tasks/:taskKey/external-review/rerun",
|
|
29174
|
+
"POST /projects/:projectId/tasks/:taskKey/external-review/start",
|
|
29175
|
+
"POST /projects/:projectId/tasks/:taskKey/force-done",
|
|
29176
|
+
"POST /projects/:projectId/tasks/:taskKey/open-pr",
|
|
29177
|
+
"POST /projects/:projectId/tasks/:taskKey/pause",
|
|
29178
|
+
"POST /projects/:projectId/tasks/:taskKey/publish-branch",
|
|
29179
|
+
"POST /projects/:projectId/tasks/:taskKey/reject",
|
|
29180
|
+
"POST /projects/:projectId/tasks/:taskKey/restart",
|
|
29181
|
+
"POST /projects/:projectId/tasks/:taskKey/retry",
|
|
29182
|
+
"POST /projects/:projectId/tasks/:taskKey/to-backlog",
|
|
29183
|
+
"POST /projects/:projectId/tasks/:taskKey/tracker-status",
|
|
29184
|
+
"POST /projects/:projectId/tasks/:taskKey/unarchive",
|
|
29185
|
+
"POST /projects/:projectId/tracker-sync",
|
|
29186
|
+
"POST /runner/reg-token",
|
|
29187
|
+
"POST /sandbox/reg-token",
|
|
29188
|
+
"PUT /api/admin/farms/:farmId/quota",
|
|
29189
|
+
"PUT /api/admin/settings",
|
|
29190
|
+
"PUT /api/notifications/settings",
|
|
29191
|
+
"PUT /projects/:projectId/adr/:file",
|
|
29192
|
+
"PUT /projects/:projectId/prompts/:role",
|
|
29193
|
+
"PUT /projects/:projectId/tasks/:taskKey/branch-name",
|
|
29194
|
+
"PUT /projects/:projectId/tasks/:taskKey/cluster-base",
|
|
29195
|
+
"PUT /projects/:projectId/tasks/:taskKey/commit-summary",
|
|
29196
|
+
"PUT /projects/:projectId/tasks/:taskKey/description",
|
|
29197
|
+
"PUT /projects/:projectId/tasks/:taskKey/solution"
|
|
29198
|
+
];
|
|
29199
|
+
|
|
29035
29200
|
// src/http/browser-security.ts
|
|
29036
29201
|
init_auth();
|
|
29037
29202
|
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
@@ -31024,6 +31189,28 @@ async function apiV1MutationRoutes(app, deps) {
|
|
|
31024
31189
|
}
|
|
31025
31190
|
}
|
|
31026
31191
|
);
|
|
31192
|
+
app.post(
|
|
31193
|
+
"/workers/registration-token",
|
|
31194
|
+
routeMeta({
|
|
31195
|
+
surface: "application",
|
|
31196
|
+
operationId: "worker.issueRegistrationToken",
|
|
31197
|
+
authPolicy: "application_actor",
|
|
31198
|
+
resourcePolicy: "account.manage",
|
|
31199
|
+
stability: "preview",
|
|
31200
|
+
summary: "Mint a single-use registration token for a worker of this account"
|
|
31201
|
+
}),
|
|
31202
|
+
async (req, reply) => {
|
|
31203
|
+
const requestId = requestIdOf(req);
|
|
31204
|
+
try {
|
|
31205
|
+
const actor = await actorOr401({ db, config }, req, reply, requestId);
|
|
31206
|
+
if (!actor) return reply;
|
|
31207
|
+
const issued = await commands.issueWorkerRegistrationToken(actor);
|
|
31208
|
+
return reply.status(201).send(issued);
|
|
31209
|
+
} catch (err) {
|
|
31210
|
+
return sendError(reply, err, requestId);
|
|
31211
|
+
}
|
|
31212
|
+
}
|
|
31213
|
+
);
|
|
31027
31214
|
app.post(
|
|
31028
31215
|
"/projects/:projectId/init-status",
|
|
31029
31216
|
routeMeta({
|
|
@@ -34145,6 +34332,8 @@ function requestGhRunnersDelete(runnerId, projectId, sealedBundle, timeoutMs) {
|
|
|
34145
34332
|
init_farm2();
|
|
34146
34333
|
init_runner2();
|
|
34147
34334
|
init_init_status();
|
|
34335
|
+
init_sandbox_reg_tokens();
|
|
34336
|
+
init_abuse_throttle();
|
|
34148
34337
|
|
|
34149
34338
|
// src/projects/forget.ts
|
|
34150
34339
|
init_legacy_cred_bundles();
|
|
@@ -34426,6 +34615,7 @@ function nodeUnavailable(capability, err) {
|
|
|
34426
34615
|
function taskState(row) {
|
|
34427
34616
|
return { ...row, revision: String(row.updatedAt) };
|
|
34428
34617
|
}
|
|
34618
|
+
var WORKER_REG_TOKEN_MINTS = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
|
|
34429
34619
|
function createWriteModel(db, log, hooks = {}) {
|
|
34430
34620
|
async function readTaskRow(projectId, key) {
|
|
34431
34621
|
const row = await db.get(
|
|
@@ -35108,6 +35298,16 @@ function createWriteModel(db, log, hooks = {}) {
|
|
|
35108
35298
|
log.info({ projectId, jobId, kind }, kind === "backfill" ? "backfill job enqueued" : "index job enqueued");
|
|
35109
35299
|
return ok({ jobId });
|
|
35110
35300
|
},
|
|
35301
|
+
async issueWorkerRegistrationToken({ owner }) {
|
|
35302
|
+
if (owner.email === null) return { ok: false, refusal: "unprocessable", reason: "account_has_no_email" };
|
|
35303
|
+
if (!WORKER_REG_TOKEN_MINTS.take(`email:${owner.email}`)) {
|
|
35304
|
+
log.warn({ email: owner.email }, "ephemeral sandbox reg token mint throttled");
|
|
35305
|
+
return { ok: false, refusal: "rate_limited" };
|
|
35306
|
+
}
|
|
35307
|
+
const issued = await issueEphemeralSandboxRegToken(db, owner.email);
|
|
35308
|
+
log.info({ email: owner.email }, "ephemeral sandbox reg token issued for a client");
|
|
35309
|
+
return ok(issued);
|
|
35310
|
+
},
|
|
35111
35311
|
async reportInitStatus({ projectId, phase, error }) {
|
|
35112
35312
|
const recorded = await advanceInitPhase(db, projectId, phase);
|
|
35113
35313
|
if (error !== null) {
|
|
@@ -37925,6 +38125,7 @@ function clearPasswordAttempts(key) {
|
|
|
37925
38125
|
|
|
37926
38126
|
// src/routes/auth.ts
|
|
37927
38127
|
init_users();
|
|
38128
|
+
init_route_registry();
|
|
37928
38129
|
async function authRoutes(app, deps) {
|
|
37929
38130
|
const { db, config, mailer } = deps;
|
|
37930
38131
|
const loginByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 30 });
|
|
@@ -37965,18 +38166,21 @@ async function authRoutes(app, deps) {
|
|
|
37965
38166
|
function registerAccountRoutes() {
|
|
37966
38167
|
app.post(
|
|
37967
38168
|
"/auth/login",
|
|
37968
|
-
{
|
|
37969
|
-
|
|
37970
|
-
|
|
37971
|
-
|
|
37972
|
-
|
|
37973
|
-
|
|
37974
|
-
|
|
37975
|
-
|
|
37976
|
-
|
|
38169
|
+
routeMeta({
|
|
38170
|
+
surface: "bootstrap",
|
|
38171
|
+
operationId: "auth.login",
|
|
38172
|
+
authPolicy: "public",
|
|
38173
|
+
stability: "preview",
|
|
38174
|
+
summary: "Sign in: send a magic link",
|
|
38175
|
+
requestSchema: {
|
|
38176
|
+
type: "object",
|
|
38177
|
+
required: ["email"],
|
|
38178
|
+
properties: {
|
|
38179
|
+
email: { type: "string", minLength: 3, maxLength: 254 },
|
|
38180
|
+
pwa_nonce: { type: "string", minLength: 1, maxLength: 64 }
|
|
37977
38181
|
}
|
|
37978
38182
|
}
|
|
37979
|
-
},
|
|
38183
|
+
}),
|
|
37980
38184
|
async (req, reply) => {
|
|
37981
38185
|
const email = (req.body.email ?? "").trim().toLowerCase();
|
|
37982
38186
|
if (loginThrottled(email, req.ip)) {
|
|
@@ -37994,18 +38198,21 @@ async function authRoutes(app, deps) {
|
|
|
37994
38198
|
);
|
|
37995
38199
|
app.post(
|
|
37996
38200
|
"/auth/login-start",
|
|
37997
|
-
{
|
|
37998
|
-
|
|
37999
|
-
|
|
38000
|
-
|
|
38001
|
-
|
|
38002
|
-
|
|
38003
|
-
|
|
38004
|
-
|
|
38005
|
-
|
|
38201
|
+
routeMeta({
|
|
38202
|
+
surface: "bootstrap",
|
|
38203
|
+
operationId: "auth.loginStart",
|
|
38204
|
+
authPolicy: "public",
|
|
38205
|
+
stability: "preview",
|
|
38206
|
+
summary: "Start a sign-in, without saying whether the account exists",
|
|
38207
|
+
requestSchema: {
|
|
38208
|
+
type: "object",
|
|
38209
|
+
required: ["email"],
|
|
38210
|
+
properties: {
|
|
38211
|
+
email: { type: "string", minLength: 3, maxLength: 254 },
|
|
38212
|
+
pwa_nonce: { type: "string", minLength: 1, maxLength: 64 }
|
|
38006
38213
|
}
|
|
38007
38214
|
}
|
|
38008
|
-
},
|
|
38215
|
+
}),
|
|
38009
38216
|
async (req, reply) => {
|
|
38010
38217
|
const email = (req.body.email ?? "").trim().toLowerCase();
|
|
38011
38218
|
if (loginThrottled(email, req.ip)) {
|
|
@@ -38027,18 +38234,21 @@ async function authRoutes(app, deps) {
|
|
|
38027
38234
|
);
|
|
38028
38235
|
app.post(
|
|
38029
38236
|
"/auth/login-password",
|
|
38030
|
-
{
|
|
38031
|
-
|
|
38032
|
-
|
|
38033
|
-
|
|
38034
|
-
|
|
38035
|
-
|
|
38036
|
-
|
|
38037
|
-
|
|
38038
|
-
|
|
38237
|
+
routeMeta({
|
|
38238
|
+
surface: "bootstrap",
|
|
38239
|
+
operationId: "auth.loginPassword",
|
|
38240
|
+
authPolicy: "public",
|
|
38241
|
+
stability: "preview",
|
|
38242
|
+
summary: "Sign in with a password",
|
|
38243
|
+
requestSchema: {
|
|
38244
|
+
type: "object",
|
|
38245
|
+
required: ["email", "password"],
|
|
38246
|
+
properties: {
|
|
38247
|
+
email: { type: "string", minLength: 3, maxLength: 254 },
|
|
38248
|
+
password: { type: "string", minLength: 1, maxLength: MAX_PASSWORD_LENGTH }
|
|
38039
38249
|
}
|
|
38040
38250
|
}
|
|
38041
|
-
},
|
|
38251
|
+
}),
|
|
38042
38252
|
async (req, reply) => {
|
|
38043
38253
|
const email = (req.body.email ?? "").trim().toLowerCase();
|
|
38044
38254
|
const password = req.body.password ?? "";
|
|
@@ -38062,15 +38272,18 @@ async function authRoutes(app, deps) {
|
|
|
38062
38272
|
);
|
|
38063
38273
|
app.post(
|
|
38064
38274
|
"/auth/set-password",
|
|
38065
|
-
{
|
|
38066
|
-
|
|
38067
|
-
|
|
38068
|
-
|
|
38069
|
-
|
|
38070
|
-
|
|
38071
|
-
|
|
38275
|
+
routeMeta({
|
|
38276
|
+
surface: "bootstrap",
|
|
38277
|
+
operationId: "auth.setPassword",
|
|
38278
|
+
authPolicy: "session",
|
|
38279
|
+
stability: "preview",
|
|
38280
|
+
summary: "Set the signed-in account's password",
|
|
38281
|
+
requestSchema: {
|
|
38282
|
+
type: "object",
|
|
38283
|
+
required: ["password"],
|
|
38284
|
+
properties: { password: { type: "string", minLength: 1, maxLength: 512 } }
|
|
38072
38285
|
}
|
|
38073
|
-
},
|
|
38286
|
+
}),
|
|
38074
38287
|
async (req, reply) => {
|
|
38075
38288
|
const session = await requireSession(db, req, reply);
|
|
38076
38289
|
if (!session) return reply;
|
|
@@ -38089,7 +38302,14 @@ async function authRoutes(app, deps) {
|
|
|
38089
38302
|
return reply.send({ status: "ok" });
|
|
38090
38303
|
}
|
|
38091
38304
|
);
|
|
38092
|
-
app.get("/auth/verify",
|
|
38305
|
+
app.get("/auth/verify", routeMeta({
|
|
38306
|
+
surface: "bootstrap",
|
|
38307
|
+
operationId: "auth.verify",
|
|
38308
|
+
authPolicy: "public",
|
|
38309
|
+
// A redirect from an email link, not a JSON operation to generate a client for.
|
|
38310
|
+
stability: "internal",
|
|
38311
|
+
summary: "Redeem a magic link"
|
|
38312
|
+
}), async (req, reply) => {
|
|
38093
38313
|
const wantsHtml = (req.headers.accept ?? "").toLowerCase().includes("text/html");
|
|
38094
38314
|
const token = (req.query.token ?? "").trim();
|
|
38095
38315
|
if (!token) {
|
|
@@ -38111,7 +38331,14 @@ async function authRoutes(app, deps) {
|
|
|
38111
38331
|
});
|
|
38112
38332
|
}
|
|
38113
38333
|
function registerSessionRoutes() {
|
|
38114
|
-
app.get("/auth/me",
|
|
38334
|
+
app.get("/auth/me", routeMeta({
|
|
38335
|
+
surface: "bootstrap",
|
|
38336
|
+
operationId: "auth.me",
|
|
38337
|
+
// A cookie, or the single-owner's credential on the local profile.
|
|
38338
|
+
authPolicy: "application_actor",
|
|
38339
|
+
stability: "preview",
|
|
38340
|
+
summary: "Who is signed in"
|
|
38341
|
+
}), async (req, reply) => {
|
|
38115
38342
|
const session = await readSession(db, req);
|
|
38116
38343
|
if (!session) return reply.status(401).send({ error: "no_session" });
|
|
38117
38344
|
const user = await findUser(db, session.email);
|
|
@@ -38123,7 +38350,13 @@ async function authRoutes(app, deps) {
|
|
|
38123
38350
|
singleOwner
|
|
38124
38351
|
};
|
|
38125
38352
|
});
|
|
38126
|
-
app.post("/auth/logout",
|
|
38353
|
+
app.post("/auth/logout", routeMeta({
|
|
38354
|
+
surface: "bootstrap",
|
|
38355
|
+
operationId: "auth.logout",
|
|
38356
|
+
authPolicy: "public",
|
|
38357
|
+
stability: "preview",
|
|
38358
|
+
summary: "Sign out"
|
|
38359
|
+
}), async (req, reply) => {
|
|
38127
38360
|
const cookie2 = req.cookies[SESSION_COOKIE_NAME];
|
|
38128
38361
|
if (cookie2) {
|
|
38129
38362
|
const unsigned = req.unsignCookie(cookie2);
|
|
@@ -38134,7 +38367,13 @@ async function authRoutes(app, deps) {
|
|
|
38134
38367
|
reply.clearCookie(SESSION_COOKIE_NAME, { path: "/" });
|
|
38135
38368
|
return { status: "ok" };
|
|
38136
38369
|
});
|
|
38137
|
-
app.get("/auth/pwa-ready",
|
|
38370
|
+
app.get("/auth/pwa-ready", routeMeta({
|
|
38371
|
+
surface: "bootstrap",
|
|
38372
|
+
operationId: "auth.pwaReady",
|
|
38373
|
+
authPolicy: "public",
|
|
38374
|
+
stability: "preview",
|
|
38375
|
+
summary: "Whether an installed app's sign-in has completed"
|
|
38376
|
+
}), async (req, reply) => {
|
|
38138
38377
|
const nonce = (req.query.nonce ?? "").trim();
|
|
38139
38378
|
if (!nonce) return reply.status(400).send({ error: "missing_nonce" });
|
|
38140
38379
|
const result = await consumePwaNonce(db, nonce);
|
|
@@ -38159,6 +38398,7 @@ import { nanoid as nanoid41 } from "nanoid";
|
|
|
38159
38398
|
init_settings();
|
|
38160
38399
|
init_users();
|
|
38161
38400
|
init_cli_tokens();
|
|
38401
|
+
init_route_registry();
|
|
38162
38402
|
var REQUEST_TTL_MS = 15 * 6e4;
|
|
38163
38403
|
var APPROVAL_TTL_MS = 24 * 60 * 6e4;
|
|
38164
38404
|
async function cliBootstrapRoutes(app, deps) {
|
|
@@ -38192,15 +38432,18 @@ async function cliBootstrapRoutes(app, deps) {
|
|
|
38192
38432
|
}
|
|
38193
38433
|
app.post(
|
|
38194
38434
|
"/cli/bootstrap-request",
|
|
38195
|
-
{
|
|
38196
|
-
|
|
38197
|
-
|
|
38198
|
-
|
|
38199
|
-
|
|
38200
|
-
|
|
38201
|
-
|
|
38435
|
+
routeMeta({
|
|
38436
|
+
surface: "bootstrap",
|
|
38437
|
+
operationId: "bootstrap.request",
|
|
38438
|
+
authPolicy: "public",
|
|
38439
|
+
stability: "preview",
|
|
38440
|
+
summary: "A CLI asks to be connected to an account",
|
|
38441
|
+
requestSchema: {
|
|
38442
|
+
type: "object",
|
|
38443
|
+
required: ["email"],
|
|
38444
|
+
properties: { email: { type: "string", minLength: 3, maxLength: 254 } }
|
|
38202
38445
|
}
|
|
38203
|
-
},
|
|
38446
|
+
}),
|
|
38204
38447
|
async (req, reply) => {
|
|
38205
38448
|
const email = (req.body.email ?? "").trim().toLowerCase();
|
|
38206
38449
|
if (!isValidEmail(email)) {
|
|
@@ -38248,6 +38491,14 @@ async function cliBootstrapRoutes(app, deps) {
|
|
|
38248
38491
|
);
|
|
38249
38492
|
app.get(
|
|
38250
38493
|
"/cli/authorize",
|
|
38494
|
+
routeMeta({
|
|
38495
|
+
surface: "bootstrap",
|
|
38496
|
+
operationId: "bootstrap.authorizePage",
|
|
38497
|
+
authPolicy: "public",
|
|
38498
|
+
// An HTML page, not a JSON operation to generate a client for.
|
|
38499
|
+
stability: "internal",
|
|
38500
|
+
summary: "The page where a person confirms a CLI connection"
|
|
38501
|
+
}),
|
|
38251
38502
|
async (req, reply) => {
|
|
38252
38503
|
const requestId = (req.query.request ?? "").trim();
|
|
38253
38504
|
const row = requestId ? await loadRequest(requestId) : void 0;
|
|
@@ -38296,15 +38547,19 @@ async function cliBootstrapRoutes(app, deps) {
|
|
|
38296
38547
|
);
|
|
38297
38548
|
app.post(
|
|
38298
38549
|
"/cli/authorize/confirm",
|
|
38299
|
-
{
|
|
38300
|
-
|
|
38301
|
-
|
|
38302
|
-
|
|
38303
|
-
|
|
38304
|
-
|
|
38305
|
-
|
|
38550
|
+
routeMeta({
|
|
38551
|
+
surface: "bootstrap",
|
|
38552
|
+
operationId: "bootstrap.authorizeConfirm",
|
|
38553
|
+
// A cookie, or the single-owner's credential on the local profile.
|
|
38554
|
+
authPolicy: "application_actor",
|
|
38555
|
+
stability: "preview",
|
|
38556
|
+
summary: "Confirm a CLI connection as the signed-in person",
|
|
38557
|
+
requestSchema: {
|
|
38558
|
+
type: "object",
|
|
38559
|
+
required: ["request"],
|
|
38560
|
+
properties: { request: { type: "string", minLength: 1, maxLength: 64 } }
|
|
38306
38561
|
}
|
|
38307
|
-
},
|
|
38562
|
+
}),
|
|
38308
38563
|
async (req, reply) => {
|
|
38309
38564
|
const session = await readSession(db, req);
|
|
38310
38565
|
if (!session || !await isAllowedLogin(db, config, session.email)) {
|
|
@@ -38322,15 +38577,18 @@ async function cliBootstrapRoutes(app, deps) {
|
|
|
38322
38577
|
);
|
|
38323
38578
|
app.post(
|
|
38324
38579
|
"/cli/authorize/email",
|
|
38325
|
-
{
|
|
38326
|
-
|
|
38327
|
-
|
|
38328
|
-
|
|
38329
|
-
|
|
38330
|
-
|
|
38331
|
-
|
|
38580
|
+
routeMeta({
|
|
38581
|
+
surface: "bootstrap",
|
|
38582
|
+
operationId: "bootstrap.authorizeEmail",
|
|
38583
|
+
authPolicy: "public",
|
|
38584
|
+
stability: "preview",
|
|
38585
|
+
summary: "Confirm a CLI connection by email instead",
|
|
38586
|
+
requestSchema: {
|
|
38587
|
+
type: "object",
|
|
38588
|
+
required: ["request"],
|
|
38589
|
+
properties: { request: { type: "string", minLength: 1, maxLength: 64 } }
|
|
38332
38590
|
}
|
|
38333
|
-
},
|
|
38591
|
+
}),
|
|
38334
38592
|
async (req, reply) => {
|
|
38335
38593
|
const requestId = (req.body.request ?? "").trim();
|
|
38336
38594
|
const row = await loadRequest(requestId);
|
|
@@ -38348,6 +38606,13 @@ async function cliBootstrapRoutes(app, deps) {
|
|
|
38348
38606
|
);
|
|
38349
38607
|
app.get(
|
|
38350
38608
|
"/cli/bootstrap-confirm",
|
|
38609
|
+
routeMeta({
|
|
38610
|
+
surface: "bootstrap",
|
|
38611
|
+
operationId: "bootstrap.confirm",
|
|
38612
|
+
authPolicy: "public",
|
|
38613
|
+
stability: "internal",
|
|
38614
|
+
summary: "Redeem the CLI connection link from the email"
|
|
38615
|
+
}),
|
|
38351
38616
|
async (req, reply) => {
|
|
38352
38617
|
const token = (req.query.token ?? "").trim();
|
|
38353
38618
|
reply.type("text/html");
|
|
@@ -38398,6 +38663,13 @@ async function cliBootstrapRoutes(app, deps) {
|
|
|
38398
38663
|
);
|
|
38399
38664
|
app.get(
|
|
38400
38665
|
"/cli/bootstrap-poll",
|
|
38666
|
+
routeMeta({
|
|
38667
|
+
surface: "bootstrap",
|
|
38668
|
+
operationId: "bootstrap.poll",
|
|
38669
|
+
authPolicy: "public",
|
|
38670
|
+
stability: "preview",
|
|
38671
|
+
summary: "A CLI polls whether its connection was confirmed"
|
|
38672
|
+
}),
|
|
38401
38673
|
async (req, reply) => {
|
|
38402
38674
|
const requestId = (req.query.request ?? "").trim();
|
|
38403
38675
|
if (!requestId) return reply.status(400).send({ error: "missing_request" });
|
|
@@ -38437,13 +38709,20 @@ import { nanoid as nanoid42 } from "nanoid";
|
|
|
38437
38709
|
init_auth();
|
|
38438
38710
|
init_settings();
|
|
38439
38711
|
init_users();
|
|
38712
|
+
init_route_registry();
|
|
38440
38713
|
var CHALLENGE_TTL_MS = 10 * 6e4;
|
|
38441
38714
|
var MIN_FILL_MS = 2e3;
|
|
38442
38715
|
async function registrationRoutes(app, deps) {
|
|
38443
38716
|
const { db, config, mailer } = deps;
|
|
38444
38717
|
const submitByIp = createAbuseThrottle({ windowMs: 60 * 6e4, max: 10 });
|
|
38445
38718
|
const challengeByIp = createAbuseThrottle({ windowMs: 60 * 6e4, max: 60 });
|
|
38446
|
-
app.get("/auth/register-challenge",
|
|
38719
|
+
app.get("/auth/register-challenge", routeMeta({
|
|
38720
|
+
surface: "bootstrap",
|
|
38721
|
+
operationId: "auth.registerChallenge",
|
|
38722
|
+
authPolicy: "public",
|
|
38723
|
+
stability: "preview",
|
|
38724
|
+
summary: "The challenge a registration form answers"
|
|
38725
|
+
}), async (req, reply) => {
|
|
38447
38726
|
if (!challengeByIp.take(`ip:${req.ip}`)) {
|
|
38448
38727
|
return reply.status(429).send({ error: "too_many_requests" });
|
|
38449
38728
|
}
|
|
@@ -38457,21 +38736,24 @@ async function registrationRoutes(app, deps) {
|
|
|
38457
38736
|
});
|
|
38458
38737
|
app.post(
|
|
38459
38738
|
"/auth/register",
|
|
38460
|
-
{
|
|
38461
|
-
|
|
38462
|
-
|
|
38463
|
-
|
|
38464
|
-
|
|
38465
|
-
|
|
38466
|
-
|
|
38467
|
-
|
|
38468
|
-
|
|
38469
|
-
|
|
38470
|
-
|
|
38471
|
-
}
|
|
38739
|
+
routeMeta({
|
|
38740
|
+
surface: "bootstrap",
|
|
38741
|
+
operationId: "auth.register",
|
|
38742
|
+
authPolicy: "public",
|
|
38743
|
+
stability: "preview",
|
|
38744
|
+
summary: "Ask for an account",
|
|
38745
|
+
requestSchema: {
|
|
38746
|
+
type: "object",
|
|
38747
|
+
required: ["email", "challengeId"],
|
|
38748
|
+
properties: {
|
|
38749
|
+
email: { type: "string", minLength: 3, maxLength: 254 },
|
|
38750
|
+
name: { type: "string", maxLength: 200 },
|
|
38751
|
+
note: { type: "string", maxLength: 1e3 },
|
|
38752
|
+
challengeId: { type: "string", minLength: 1, maxLength: 64 },
|
|
38753
|
+
website: { type: "string", maxLength: 200 }
|
|
38472
38754
|
}
|
|
38473
38755
|
}
|
|
38474
|
-
},
|
|
38756
|
+
}),
|
|
38475
38757
|
async (req, reply) => {
|
|
38476
38758
|
const email = (req.body.email ?? "").trim().toLowerCase();
|
|
38477
38759
|
const name = (req.body.name ?? "").trim() || null;
|
|
@@ -38519,7 +38801,13 @@ async function registrationRoutes(app, deps) {
|
|
|
38519
38801
|
return reply.status(202).send({ status: "pending" });
|
|
38520
38802
|
}
|
|
38521
38803
|
);
|
|
38522
|
-
app.get("/auth/register-approve",
|
|
38804
|
+
app.get("/auth/register-approve", routeMeta({
|
|
38805
|
+
surface: "bootstrap",
|
|
38806
|
+
operationId: "auth.registerApprove",
|
|
38807
|
+
authPolicy: "public",
|
|
38808
|
+
stability: "internal",
|
|
38809
|
+
summary: "Approve a registration request from its link"
|
|
38810
|
+
}), async (req, reply) => {
|
|
38523
38811
|
const token = (req.query.token ?? "").trim();
|
|
38524
38812
|
const row = token ? await db.get(`SELECT id, email, name, note, status, approve_token as approveToken,
|
|
38525
38813
|
reject_token as rejectToken, created_at as createdAt,
|
|
@@ -38539,7 +38827,13 @@ async function registrationRoutes(app, deps) {
|
|
|
38539
38827
|
)
|
|
38540
38828
|
);
|
|
38541
38829
|
});
|
|
38542
|
-
app.get("/auth/register-reject",
|
|
38830
|
+
app.get("/auth/register-reject", routeMeta({
|
|
38831
|
+
surface: "bootstrap",
|
|
38832
|
+
operationId: "auth.registerReject",
|
|
38833
|
+
authPolicy: "public",
|
|
38834
|
+
stability: "internal",
|
|
38835
|
+
summary: "Reject a registration request from its link"
|
|
38836
|
+
}), async (req, reply) => {
|
|
38543
38837
|
const token = (req.query.token ?? "").trim();
|
|
38544
38838
|
const row = token ? await db.get(`SELECT id, email, name, status FROM registration_requests WHERE reject_token = ?`, token) : void 0;
|
|
38545
38839
|
if (!row) {
|
|
@@ -38741,6 +39035,7 @@ function safeNext(next) {
|
|
|
38741
39035
|
}
|
|
38742
39036
|
|
|
38743
39037
|
// src/routes/install.ts
|
|
39038
|
+
init_route_registry();
|
|
38744
39039
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
38745
39040
|
import { dirname as dirname3, resolve as resolve2 } from "node:path";
|
|
38746
39041
|
import { existsSync as existsSync6 } from "node:fs";
|
|
@@ -38751,7 +39046,14 @@ var SCRIPT_PATH = [
|
|
|
38751
39046
|
resolve2(MODULE_DIR2, "../../../deploy/install-sandbox.sh")
|
|
38752
39047
|
].find((p) => existsSync6(p)) ?? resolve2(MODULE_DIR2, "../../../deploy/install-sandbox.sh");
|
|
38753
39048
|
async function installRoutes(app) {
|
|
38754
|
-
app.get("/install-sandbox.sh",
|
|
39049
|
+
app.get("/install-sandbox.sh", routeMeta({
|
|
39050
|
+
surface: "static",
|
|
39051
|
+
operationId: "ops.installSandboxScript",
|
|
39052
|
+
authPolicy: "public",
|
|
39053
|
+
// A shell script, not a JSON operation.
|
|
39054
|
+
stability: "internal",
|
|
39055
|
+
summary: "The SANDBOX install script"
|
|
39056
|
+
}), async (_req, reply) => {
|
|
38755
39057
|
try {
|
|
38756
39058
|
const body = await readFile(SCRIPT_PATH, "utf8");
|
|
38757
39059
|
return reply.type("text/x-shellscript; charset=utf-8").send(body);
|
|
@@ -39522,10 +39824,18 @@ async function negotiateJobCredentials(db, spec, workerId, log, issuer) {
|
|
|
39522
39824
|
// src/routes/jobs.ts
|
|
39523
39825
|
init_sandbox2();
|
|
39524
39826
|
init_ws();
|
|
39827
|
+
init_route_registry();
|
|
39525
39828
|
async function jobsRoutes(app, deps) {
|
|
39526
39829
|
const { db, config } = deps;
|
|
39527
39830
|
const notifyDeps = deps.notify;
|
|
39528
|
-
app.post("/jobs/claim",
|
|
39831
|
+
app.post("/node/v1/jobs/claim", routeMeta({
|
|
39832
|
+
surface: "node",
|
|
39833
|
+
operationId: "node.jobs.claim",
|
|
39834
|
+
authPolicy: "node_actor",
|
|
39835
|
+
resourcePolicy: "node.connect",
|
|
39836
|
+
stability: "preview",
|
|
39837
|
+
summary: "Claim the next job for this SANDBOX (long poll)"
|
|
39838
|
+
}), async (req, reply) => {
|
|
39529
39839
|
const sandbox = await authenticateSandboxByBearer(db, req.headers.authorization);
|
|
39530
39840
|
if (!sandbox) {
|
|
39531
39841
|
return reply.status(401).send({ error: "unauthorized" });
|
|
@@ -39579,8 +39889,16 @@ async function jobsRoutes(app, deps) {
|
|
|
39579
39889
|
}
|
|
39580
39890
|
});
|
|
39581
39891
|
app.post(
|
|
39582
|
-
"/jobs/heartbeat",
|
|
39892
|
+
"/node/v1/jobs/heartbeat",
|
|
39583
39893
|
{
|
|
39894
|
+
...routeMeta({
|
|
39895
|
+
surface: "node",
|
|
39896
|
+
operationId: "node.jobs.heartbeat",
|
|
39897
|
+
authPolicy: "node_actor",
|
|
39898
|
+
resourcePolicy: "node.connect",
|
|
39899
|
+
stability: "preview",
|
|
39900
|
+
summary: "Keep a claimed job's lease alive"
|
|
39901
|
+
}),
|
|
39584
39902
|
schema: {
|
|
39585
39903
|
body: {
|
|
39586
39904
|
type: "object",
|
|
@@ -39642,8 +39960,16 @@ async function jobsRoutes(app, deps) {
|
|
|
39642
39960
|
}
|
|
39643
39961
|
);
|
|
39644
39962
|
app.post(
|
|
39645
|
-
"/jobs/release",
|
|
39963
|
+
"/node/v1/jobs/release",
|
|
39646
39964
|
{
|
|
39965
|
+
...routeMeta({
|
|
39966
|
+
surface: "node",
|
|
39967
|
+
operationId: "node.jobs.release",
|
|
39968
|
+
authPolicy: "node_actor",
|
|
39969
|
+
resourcePolicy: "node.connect",
|
|
39970
|
+
stability: "preview",
|
|
39971
|
+
summary: "Hand a claimed job back to the queue"
|
|
39972
|
+
}),
|
|
39647
39973
|
schema: {
|
|
39648
39974
|
body: {
|
|
39649
39975
|
type: "object",
|
|
@@ -39679,8 +40005,16 @@ async function jobsRoutes(app, deps) {
|
|
|
39679
40005
|
}
|
|
39680
40006
|
);
|
|
39681
40007
|
app.post(
|
|
39682
|
-
"/jobs/result",
|
|
40008
|
+
"/node/v1/jobs/result",
|
|
39683
40009
|
{
|
|
40010
|
+
...routeMeta({
|
|
40011
|
+
surface: "node",
|
|
40012
|
+
operationId: "node.jobs.result",
|
|
40013
|
+
authPolicy: "node_actor",
|
|
40014
|
+
resourcePolicy: "node.connect",
|
|
40015
|
+
stability: "preview",
|
|
40016
|
+
summary: "Report a finished job"
|
|
40017
|
+
}),
|
|
39684
40018
|
schema: {
|
|
39685
40019
|
body: {
|
|
39686
40020
|
type: "object",
|
|
@@ -41454,9 +41788,11 @@ async function buildServer(config, db) {
|
|
|
41454
41788
|
}
|
|
41455
41789
|
});
|
|
41456
41790
|
}
|
|
41457
|
-
const routeRegistry = installRouteGuard(app, "
|
|
41458
|
-
|
|
41459
|
-
|
|
41791
|
+
const routeRegistry = installRouteGuard(app, "enforce", { warn: (message) => app.log.warn(message) }, [
|
|
41792
|
+
...LEGACY_ROUTES,
|
|
41793
|
+
...STATIC_PLUGIN_ROUTES
|
|
41794
|
+
]);
|
|
41795
|
+
app.decorate("routeRegistry", routeRegistry);
|
|
41460
41796
|
const mailer = buildMailer(loadMailerConfig(), app.log);
|
|
41461
41797
|
app.log.info({ mailer: mailer.mode }, "mailer mode");
|
|
41462
41798
|
const notifyDeps = {
|
|
@@ -41678,10 +42014,9 @@ async function buildServer(config, db) {
|
|
|
41678
42014
|
app.log.info(
|
|
41679
42015
|
{
|
|
41680
42016
|
classified: routeRegistry.list().length,
|
|
41681
|
-
|
|
41682
|
-
invalid: routeRegistry.invalidCount()
|
|
42017
|
+
legacy: routeRegistry.unclassifiedRoutes().filter((r) => !STATIC_PLUGIN_ROUTES.includes(r)).length
|
|
41683
42018
|
},
|
|
41684
|
-
"route registry: routes still
|
|
42019
|
+
"route registry: legacy routes still served for the browser"
|
|
41685
42020
|
);
|
|
41686
42021
|
});
|
|
41687
42022
|
await registerSpaHosts(app, {
|