@maxceem/agw 0.1.3 → 0.1.5
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/README.md +1 -1
- package/dist/agw.mjs +62 -19
- package/dist/release/{0.1.3/console/assets/endpoints-CpkgQRHP.js → 0.1.5/console/assets/endpoints-DApt6mPC.js} +1 -1
- package/dist/release/0.1.5/console/assets/index-DUtKG6Od.css +2 -0
- package/dist/release/{0.1.3/console/assets/index-CoITs7Dp.js → 0.1.5/console/assets/index-J1eP6WzX.js} +8 -8
- package/dist/release/{0.1.3/console/assets/usage-DbEmt1ps.js → 0.1.5/console/assets/usage-D26T_dIp.js} +1 -1
- package/dist/release/{0.1.3 → 0.1.5}/console/index.html +2 -2
- package/dist/release/0.1.5/manifest.json +27 -0
- package/dist/release/{0.1.3 → 0.1.5}/migrations/0000_initial.sql +0 -1
- package/dist/release/{0.1.3 → 0.1.5}/worker/index.js +271 -271
- package/dist/release-integrity.json +1 -1
- package/package.json +2 -2
- package/dist/release/0.1.3/console/assets/index-CsWBdB28.css +0 -2
- package/dist/release/0.1.3/manifest.json +0 -25
- /package/dist/release/{0.1.3 → 0.1.5}/console/.assetsignore +0 -0
- /package/dist/release/{0.1.3 → 0.1.5}/console/favicon.svg +0 -0
- /package/dist/release/{0.1.3 → 0.1.5}/wrangler.json +0 -0
|
@@ -73475,7 +73475,6 @@ var mgmtHandoff = sqliteTable(
|
|
|
73475
73475
|
initiatingCredentialId: text("initiating_credential_id").notNull(),
|
|
73476
73476
|
submissionProofHash: text("submission_proof_hash").notNull(),
|
|
73477
73477
|
pollProofHash: text("poll_proof_hash").notNull(),
|
|
73478
|
-
humanCodeHash: text("human_code_hash"),
|
|
73479
73478
|
consumedAt: integer2("consumed_at"),
|
|
73480
73479
|
outcome: text("outcome"),
|
|
73481
73480
|
expiresAt: integer2("expires_at").notNull(),
|
|
@@ -76317,9 +76316,7 @@ var CliOperationRequestSchema = external_exports.object({
|
|
|
76317
76316
|
}).strict();
|
|
76318
76317
|
var CliSubmissionRequestSchema = external_exports.object({
|
|
76319
76318
|
submissionToken: CliProofSchema,
|
|
76320
|
-
humanCode: external_exports.string().max(40).optional(),
|
|
76321
76319
|
approve: external_exports.literal(true).optional(),
|
|
76322
|
-
allowServiceAccess: external_exports.boolean().optional(),
|
|
76323
76320
|
email: external_exports.email().optional(),
|
|
76324
76321
|
password: external_exports.string().min(8).max(256).optional(),
|
|
76325
76322
|
name: external_exports.string().min(1).max(100).optional(),
|
|
@@ -76338,6 +76335,37 @@ var CliAccountSchema = external_exports.object({
|
|
|
76338
76335
|
claimed: external_exports.boolean(),
|
|
76339
76336
|
expiresAt: external_exports.string().nullable()
|
|
76340
76337
|
});
|
|
76338
|
+
var CliApprovalRefusalSchema = external_exports.enum(["session_required", "account_exists"]);
|
|
76339
|
+
var CliViewerSchema = external_exports.object({
|
|
76340
|
+
name: external_exports.string().nullable(),
|
|
76341
|
+
email: external_exports.string().nullable()
|
|
76342
|
+
});
|
|
76343
|
+
var CliBrowserDetailsResponseSchema = external_exports.object({
|
|
76344
|
+
kind: CliOperationRequestSchema.shape.kind,
|
|
76345
|
+
payload: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
76346
|
+
account: CliAccountSchema,
|
|
76347
|
+
viewer: CliViewerSchema.nullable(),
|
|
76348
|
+
/**
|
|
76349
|
+
* What stands between this browser and the Approve button, or null when
|
|
76350
|
+
* nothing does — which is every non-claim kind, since only a claim asks who
|
|
76351
|
+
* is holding the browser.
|
|
76352
|
+
*/
|
|
76353
|
+
blockedBy: CliApprovalRefusalSchema.nullable(),
|
|
76354
|
+
googleEnabled: external_exports.boolean(),
|
|
76355
|
+
expiresAt: external_exports.string()
|
|
76356
|
+
});
|
|
76357
|
+
var CliBrowserSubmitResponseSchema = external_exports.object({
|
|
76358
|
+
state: external_exports.literal("completed"),
|
|
76359
|
+
message: external_exports.string()
|
|
76360
|
+
});
|
|
76361
|
+
var CliBrowserRegisterResponseSchema = external_exports.object({
|
|
76362
|
+
token: external_exports.string().nullable().optional(),
|
|
76363
|
+
redirect: external_exports.boolean().optional()
|
|
76364
|
+
});
|
|
76365
|
+
var CliBrowserGoogleResponseSchema = external_exports.object({
|
|
76366
|
+
url: external_exports.string(),
|
|
76367
|
+
redirect: external_exports.boolean().optional()
|
|
76368
|
+
});
|
|
76341
76369
|
var CliCredentialSchema = external_exports.object({
|
|
76342
76370
|
token: external_exports.string()
|
|
76343
76371
|
});
|
|
@@ -76352,14 +76380,11 @@ var CliOperationResponseSchema = external_exports.object({
|
|
|
76352
76380
|
url: external_exports.url(),
|
|
76353
76381
|
expiresAt: external_exports.string(),
|
|
76354
76382
|
state: external_exports.enum(["pending", "completed", "failed", "expired"]),
|
|
76355
|
-
humanCode: external_exports.string().optional(),
|
|
76356
76383
|
deployment: CliDeploymentSchema
|
|
76357
76384
|
});
|
|
76358
76385
|
var CliOperationResultSchema = external_exports.object({
|
|
76359
76386
|
/** The account a claim acted on. */
|
|
76360
76387
|
accountId: external_exports.string().optional(),
|
|
76361
|
-
/** Whether the claiming human left the CLI's service access in place. */
|
|
76362
|
-
accessGranted: external_exports.boolean().optional(),
|
|
76363
76388
|
/** The stored row a provider submission created or rotated. */
|
|
76364
76389
|
provider: ProviderSummarySchema.optional(),
|
|
76365
76390
|
gateway: ProviderGatewaySummarySchema.optional()
|
|
@@ -76668,6 +76693,208 @@ async function bootstrap(c) {
|
|
|
76668
76693
|
}
|
|
76669
76694
|
__name(bootstrap, "bootstrap");
|
|
76670
76695
|
|
|
76696
|
+
// src/routes/cli/operations.ts
|
|
76697
|
+
init_errors();
|
|
76698
|
+
function browserPath(id) {
|
|
76699
|
+
return `/cli/approve/${encodeURIComponent(id)}`;
|
|
76700
|
+
}
|
|
76701
|
+
__name(browserPath, "browserPath");
|
|
76702
|
+
async function authState(c, interactive = false) {
|
|
76703
|
+
const auth = createIdentityAuth(c.env, c.req.url, {
|
|
76704
|
+
suppressDefaultOrganization: true
|
|
76705
|
+
});
|
|
76706
|
+
await auth.middleware({
|
|
76707
|
+
apiKeys: !interactive,
|
|
76708
|
+
syncCurrentOrganizationCookie: false
|
|
76709
|
+
})(c, async () => {
|
|
76710
|
+
});
|
|
76711
|
+
return c.get("authState");
|
|
76712
|
+
}
|
|
76713
|
+
__name(authState, "authState");
|
|
76714
|
+
async function challenge(c) {
|
|
76715
|
+
const row = await c.env.DB.prepare(
|
|
76716
|
+
"SELECT * FROM mgmt_handoff WHERE id=?"
|
|
76717
|
+
).bind(c.req.param("id")).first();
|
|
76718
|
+
if (!row) throw new GatewayError(404, "not_found", "Operation was not found");
|
|
76719
|
+
return row;
|
|
76720
|
+
}
|
|
76721
|
+
__name(challenge, "challenge");
|
|
76722
|
+
function nonSecretPayload(value) {
|
|
76723
|
+
if (Array.isArray(value)) {
|
|
76724
|
+
for (const item of value) nonSecretPayload(item);
|
|
76725
|
+
return;
|
|
76726
|
+
}
|
|
76727
|
+
if (value && typeof value === "object")
|
|
76728
|
+
for (const [key2, item] of Object.entries(value)) {
|
|
76729
|
+
if (/secret|password|token|api.?key/i.test(key2))
|
|
76730
|
+
throw new GatewayError(
|
|
76731
|
+
400,
|
|
76732
|
+
"invalid_request",
|
|
76733
|
+
"Secrets must be submitted in the browser form"
|
|
76734
|
+
);
|
|
76735
|
+
nonSecretPayload(item);
|
|
76736
|
+
}
|
|
76737
|
+
}
|
|
76738
|
+
__name(nonSecretPayload, "nonSecretPayload");
|
|
76739
|
+
async function createOperation(c) {
|
|
76740
|
+
const input = providerSchemaBody(
|
|
76741
|
+
CliOperationRequestSchema,
|
|
76742
|
+
await cliJson(c.req.raw)
|
|
76743
|
+
);
|
|
76744
|
+
nonSecretPayload(input.payload);
|
|
76745
|
+
const meta3 = deployment(c);
|
|
76746
|
+
const state = await authState(c);
|
|
76747
|
+
const resolved = requireOrganization(state);
|
|
76748
|
+
if (state.credentialType === "session" && c.req.header("origin") !== meta3.consoleOrigin)
|
|
76749
|
+
throw new GatewayError(
|
|
76750
|
+
403,
|
|
76751
|
+
"forbidden",
|
|
76752
|
+
"Use the first-party console for browser operations"
|
|
76753
|
+
);
|
|
76754
|
+
if (!state.actor || resolved.role !== "owner" && resolved.role !== "admin")
|
|
76755
|
+
throw new GatewayError(
|
|
76756
|
+
403,
|
|
76757
|
+
"forbidden",
|
|
76758
|
+
"Account administration is required"
|
|
76759
|
+
);
|
|
76760
|
+
const organizationId = resolved.organization.id;
|
|
76761
|
+
const userId = state.actor.id;
|
|
76762
|
+
const credentialId = state.actor.credentialId;
|
|
76763
|
+
const account = await assertAccountAccess(
|
|
76764
|
+
c.env,
|
|
76765
|
+
organizationId,
|
|
76766
|
+
input.kind === "claim" ? "claim" : "setup"
|
|
76767
|
+
);
|
|
76768
|
+
if (input.kind === "claim" && account.claimed)
|
|
76769
|
+
throw new GatewayError(409, "conflict", "Account is already claimed");
|
|
76770
|
+
const pollHash = await digest2(input.pollToken), id = `cli-operation:${pollHash}`;
|
|
76771
|
+
const requestHash = await digest2(JSON.stringify(input.payload));
|
|
76772
|
+
let value = JSON.stringify({ ...input.payload, __requestHash: requestHash });
|
|
76773
|
+
let row = await c.env.DB.prepare(
|
|
76774
|
+
"SELECT * FROM mgmt_handoff WHERE id=?"
|
|
76775
|
+
).bind(id).first();
|
|
76776
|
+
if (row && (row.kind !== input.kind || row.organization_id !== organizationId || row.initiating_user_id !== userId || JSON.parse(row.request_json).__requestHash !== requestHash))
|
|
76777
|
+
throw new GatewayError(
|
|
76778
|
+
409,
|
|
76779
|
+
"conflict",
|
|
76780
|
+
"Operation proof is already bound to a different request"
|
|
76781
|
+
);
|
|
76782
|
+
const submissionToken = await derive(input.pollToken, `browser:${meta3.id}`);
|
|
76783
|
+
if (!row) {
|
|
76784
|
+
if (input.kind !== "claim" && !input.kind.endsWith(".add")) {
|
|
76785
|
+
if (typeof input.payload.id !== "string")
|
|
76786
|
+
throw new GatewayError(
|
|
76787
|
+
400,
|
|
76788
|
+
"invalid_request",
|
|
76789
|
+
"Resource ID is required"
|
|
76790
|
+
);
|
|
76791
|
+
const gateway = input.kind.startsWith("provider-gateway.");
|
|
76792
|
+
const snapshot = await c.env.DB.prepare(
|
|
76793
|
+
gateway ? "SELECT id,type,name,config_json AS config,status,updated_at AS expectedUpdatedAt FROM provider_gateway WHERE id=? AND organization_id=?" : "SELECT id,type,name,slug,base_url AS baseUrl,provider_gateway_id AS providerGatewayId,gateway_route_json AS gatewayRoute,status,updated_at AS expectedUpdatedAt FROM provider WHERE id=? AND organization_id=?"
|
|
76794
|
+
).bind(input.payload.id, organizationId).first();
|
|
76795
|
+
if (!snapshot)
|
|
76796
|
+
throw new GatewayError(
|
|
76797
|
+
404,
|
|
76798
|
+
"not_found",
|
|
76799
|
+
"Resource was not found in this account"
|
|
76800
|
+
);
|
|
76801
|
+
value = JSON.stringify({
|
|
76802
|
+
...input.payload,
|
|
76803
|
+
expectedUpdatedAt: snapshot.expectedUpdatedAt,
|
|
76804
|
+
snapshot,
|
|
76805
|
+
__requestHash: requestHash
|
|
76806
|
+
});
|
|
76807
|
+
}
|
|
76808
|
+
if (input.kind.startsWith("provider.")) {
|
|
76809
|
+
const captured = JSON.parse(value);
|
|
76810
|
+
const snapshot = captured.snapshot;
|
|
76811
|
+
const gatewayId = Object.hasOwn(input.payload, "providerGatewayId") ? input.payload.providerGatewayId : snapshot?.providerGatewayId;
|
|
76812
|
+
if (typeof gatewayId === "string") {
|
|
76813
|
+
const gatewaySnapshot = await c.env.DB.prepare(
|
|
76814
|
+
"SELECT id,type,name,config_json AS config,status,updated_at AS expectedUpdatedAt FROM provider_gateway WHERE id=? AND organization_id=?"
|
|
76815
|
+
).bind(gatewayId, organizationId).first();
|
|
76816
|
+
if (!gatewaySnapshot)
|
|
76817
|
+
throw new GatewayError(
|
|
76818
|
+
404,
|
|
76819
|
+
"not_found",
|
|
76820
|
+
"Provider gateway was not found in this account"
|
|
76821
|
+
);
|
|
76822
|
+
value = JSON.stringify({
|
|
76823
|
+
...captured,
|
|
76824
|
+
gatewaySnapshot,
|
|
76825
|
+
expectedGatewayUpdatedAt: gatewaySnapshot.expectedUpdatedAt
|
|
76826
|
+
});
|
|
76827
|
+
}
|
|
76828
|
+
}
|
|
76829
|
+
await enforceEndpointRateLimit(
|
|
76830
|
+
c.env,
|
|
76831
|
+
`operation:${organizationId ?? c.req.header("cf-connecting-ip") ?? "local"}`,
|
|
76832
|
+
10,
|
|
76833
|
+
6e4
|
|
76834
|
+
);
|
|
76835
|
+
const now2 = Date.now();
|
|
76836
|
+
await c.env.DB.prepare(
|
|
76837
|
+
`INSERT OR IGNORE INTO mgmt_handoff(id,kind,request_json,organization_id,initiating_user_id,initiating_credential_id,submission_proof_hash,poll_proof_hash,expires_at,created_at,updated_at)
|
|
76838
|
+
SELECT ?,?,?,?,?,?,?,?,?,?,? WHERE (SELECT COUNT(*) FROM mgmt_handoff WHERE organization_id = ? AND consumed_at IS NULL AND expires_at>?) < 10`
|
|
76839
|
+
).bind(
|
|
76840
|
+
id,
|
|
76841
|
+
input.kind,
|
|
76842
|
+
value,
|
|
76843
|
+
organizationId,
|
|
76844
|
+
userId,
|
|
76845
|
+
credentialId,
|
|
76846
|
+
await digest2(submissionToken),
|
|
76847
|
+
pollHash,
|
|
76848
|
+
now2 + TTL,
|
|
76849
|
+
now2,
|
|
76850
|
+
now2,
|
|
76851
|
+
organizationId,
|
|
76852
|
+
now2
|
|
76853
|
+
).run();
|
|
76854
|
+
row = await c.env.DB.prepare(
|
|
76855
|
+
"SELECT * FROM mgmt_handoff WHERE id=?"
|
|
76856
|
+
).bind(id).first();
|
|
76857
|
+
if (!row)
|
|
76858
|
+
throw new GatewayError(
|
|
76859
|
+
429,
|
|
76860
|
+
"rate_limited",
|
|
76861
|
+
"Too many pending operations"
|
|
76862
|
+
);
|
|
76863
|
+
}
|
|
76864
|
+
return c.json({
|
|
76865
|
+
id,
|
|
76866
|
+
url: `${meta3.consoleOrigin}${browserPath(id)}#${submissionToken}`,
|
|
76867
|
+
expiresAt: new Date(row.expires_at).toISOString(),
|
|
76868
|
+
state: row.consumed_at ? "completed" : row.expires_at <= Date.now() ? "expired" : "pending",
|
|
76869
|
+
deployment: meta3
|
|
76870
|
+
});
|
|
76871
|
+
}
|
|
76872
|
+
__name(createOperation, "createOperation");
|
|
76873
|
+
async function pollOperation(c) {
|
|
76874
|
+
const row = await challenge(c);
|
|
76875
|
+
const token = c.req.header("authorization")?.replace(/^Bearer /, "");
|
|
76876
|
+
if (!await proofMatches(token, row.poll_proof_hash))
|
|
76877
|
+
throw new GatewayError(403, "forbidden", "Invalid polling proof");
|
|
76878
|
+
const base = {
|
|
76879
|
+
id: row.id,
|
|
76880
|
+
deployment: deployment(c),
|
|
76881
|
+
expiresAt: new Date(row.expires_at).toISOString()
|
|
76882
|
+
};
|
|
76883
|
+
if (row.expires_at <= Date.now())
|
|
76884
|
+
return c.json({ ...base, state: "expired" });
|
|
76885
|
+
if (!row.consumed_at) return c.json({ ...base, state: "pending" });
|
|
76886
|
+
const result = row.outcome ? JSON.parse(row.outcome) : {};
|
|
76887
|
+
const accountId = result.accountId ?? row.organization_id;
|
|
76888
|
+
const account = accountId ? await accountLifecycle(c.env, accountId) : null;
|
|
76889
|
+
return c.json({
|
|
76890
|
+
...base,
|
|
76891
|
+
state: "completed",
|
|
76892
|
+
result,
|
|
76893
|
+
account
|
|
76894
|
+
});
|
|
76895
|
+
}
|
|
76896
|
+
__name(pollOperation, "pollOperation");
|
|
76897
|
+
|
|
76671
76898
|
// src/routes/cli/provider-handoff.ts
|
|
76672
76899
|
init_errors();
|
|
76673
76900
|
|
|
@@ -80284,219 +80511,34 @@ __name(completeProviderSubmission, "completeProviderSubmission");
|
|
|
80284
80511
|
// src/routes/cli/browser.ts
|
|
80285
80512
|
init_errors();
|
|
80286
80513
|
|
|
80287
|
-
// src/routes/cli/operations.ts
|
|
80288
|
-
init_errors();
|
|
80289
|
-
async function authState(c, interactive = false) {
|
|
80290
|
-
const auth = createIdentityAuth(c.env, c.req.url, {
|
|
80291
|
-
suppressDefaultOrganization: true
|
|
80292
|
-
});
|
|
80293
|
-
await auth.middleware({
|
|
80294
|
-
apiKeys: !interactive,
|
|
80295
|
-
syncCurrentOrganizationCookie: false
|
|
80296
|
-
})(c, async () => {
|
|
80297
|
-
});
|
|
80298
|
-
return c.get("authState");
|
|
80299
|
-
}
|
|
80300
|
-
__name(authState, "authState");
|
|
80301
|
-
async function challenge(c) {
|
|
80302
|
-
const row = await c.env.DB.prepare(
|
|
80303
|
-
"SELECT * FROM mgmt_handoff WHERE id=?"
|
|
80304
|
-
).bind(c.req.param("id")).first();
|
|
80305
|
-
if (!row) throw new GatewayError(404, "not_found", "Operation was not found");
|
|
80306
|
-
return row;
|
|
80307
|
-
}
|
|
80308
|
-
__name(challenge, "challenge");
|
|
80309
|
-
function nonSecretPayload(value) {
|
|
80310
|
-
if (Array.isArray(value)) {
|
|
80311
|
-
for (const item of value) nonSecretPayload(item);
|
|
80312
|
-
return;
|
|
80313
|
-
}
|
|
80314
|
-
if (value && typeof value === "object")
|
|
80315
|
-
for (const [key2, item] of Object.entries(value)) {
|
|
80316
|
-
if (/secret|password|token|api.?key/i.test(key2))
|
|
80317
|
-
throw new GatewayError(
|
|
80318
|
-
400,
|
|
80319
|
-
"invalid_request",
|
|
80320
|
-
"Secrets must be submitted in the browser form"
|
|
80321
|
-
);
|
|
80322
|
-
nonSecretPayload(item);
|
|
80323
|
-
}
|
|
80324
|
-
}
|
|
80325
|
-
__name(nonSecretPayload, "nonSecretPayload");
|
|
80326
|
-
async function createOperation(c) {
|
|
80327
|
-
const input = providerSchemaBody(
|
|
80328
|
-
CliOperationRequestSchema,
|
|
80329
|
-
await cliJson(c.req.raw)
|
|
80330
|
-
);
|
|
80331
|
-
nonSecretPayload(input.payload);
|
|
80332
|
-
const meta3 = deployment(c);
|
|
80333
|
-
const state = await authState(c);
|
|
80334
|
-
const resolved = requireOrganization(state);
|
|
80335
|
-
if (state.credentialType === "session" && c.req.header("origin") !== meta3.consoleOrigin)
|
|
80336
|
-
throw new GatewayError(
|
|
80337
|
-
403,
|
|
80338
|
-
"forbidden",
|
|
80339
|
-
"Use the first-party console for browser operations"
|
|
80340
|
-
);
|
|
80341
|
-
if (!state.actor || resolved.role !== "owner" && resolved.role !== "admin")
|
|
80342
|
-
throw new GatewayError(
|
|
80343
|
-
403,
|
|
80344
|
-
"forbidden",
|
|
80345
|
-
"Account administration is required"
|
|
80346
|
-
);
|
|
80347
|
-
const organizationId = resolved.organization.id;
|
|
80348
|
-
const userId = state.actor.id;
|
|
80349
|
-
const credentialId = state.actor.credentialId;
|
|
80350
|
-
const account = await assertAccountAccess(
|
|
80351
|
-
c.env,
|
|
80352
|
-
organizationId,
|
|
80353
|
-
input.kind === "claim" ? "claim" : "setup"
|
|
80354
|
-
);
|
|
80355
|
-
if (input.kind === "claim" && account.claimed)
|
|
80356
|
-
throw new GatewayError(409, "conflict", "Account is already claimed");
|
|
80357
|
-
const pollHash = await digest2(input.pollToken), id = `cli-operation:${pollHash}`;
|
|
80358
|
-
const requestHash = await digest2(JSON.stringify(input.payload));
|
|
80359
|
-
let value = JSON.stringify({ ...input.payload, __requestHash: requestHash });
|
|
80360
|
-
let row = await c.env.DB.prepare(
|
|
80361
|
-
"SELECT * FROM mgmt_handoff WHERE id=?"
|
|
80362
|
-
).bind(id).first();
|
|
80363
|
-
if (row && (row.kind !== input.kind || row.organization_id !== organizationId || row.initiating_user_id !== userId || JSON.parse(row.request_json).__requestHash !== requestHash))
|
|
80364
|
-
throw new GatewayError(
|
|
80365
|
-
409,
|
|
80366
|
-
"conflict",
|
|
80367
|
-
"Operation proof is already bound to a different request"
|
|
80368
|
-
);
|
|
80369
|
-
const submissionToken = await derive(input.pollToken, `browser:${meta3.id}`);
|
|
80370
|
-
const humanCode = (await derive(input.pollToken, `human:${meta3.id}`)).slice(0, 12).toUpperCase();
|
|
80371
|
-
if (!row) {
|
|
80372
|
-
if (input.kind !== "claim" && !input.kind.endsWith(".add")) {
|
|
80373
|
-
if (typeof input.payload.id !== "string")
|
|
80374
|
-
throw new GatewayError(
|
|
80375
|
-
400,
|
|
80376
|
-
"invalid_request",
|
|
80377
|
-
"Resource ID is required"
|
|
80378
|
-
);
|
|
80379
|
-
const gateway = input.kind.startsWith("provider-gateway.");
|
|
80380
|
-
const snapshot = await c.env.DB.prepare(
|
|
80381
|
-
gateway ? "SELECT id,type,name,config_json AS config,status,updated_at AS expectedUpdatedAt FROM provider_gateway WHERE id=? AND organization_id=?" : "SELECT id,type,name,slug,base_url AS baseUrl,provider_gateway_id AS providerGatewayId,gateway_route_json AS gatewayRoute,status,updated_at AS expectedUpdatedAt FROM provider WHERE id=? AND organization_id=?"
|
|
80382
|
-
).bind(input.payload.id, organizationId).first();
|
|
80383
|
-
if (!snapshot)
|
|
80384
|
-
throw new GatewayError(
|
|
80385
|
-
404,
|
|
80386
|
-
"not_found",
|
|
80387
|
-
"Resource was not found in this account"
|
|
80388
|
-
);
|
|
80389
|
-
value = JSON.stringify({
|
|
80390
|
-
...input.payload,
|
|
80391
|
-
expectedUpdatedAt: snapshot.expectedUpdatedAt,
|
|
80392
|
-
snapshot,
|
|
80393
|
-
__requestHash: requestHash
|
|
80394
|
-
});
|
|
80395
|
-
}
|
|
80396
|
-
if (input.kind.startsWith("provider.")) {
|
|
80397
|
-
const captured = JSON.parse(value);
|
|
80398
|
-
const snapshot = captured.snapshot;
|
|
80399
|
-
const gatewayId = Object.hasOwn(input.payload, "providerGatewayId") ? input.payload.providerGatewayId : snapshot?.providerGatewayId;
|
|
80400
|
-
if (typeof gatewayId === "string") {
|
|
80401
|
-
const gatewaySnapshot = await c.env.DB.prepare(
|
|
80402
|
-
"SELECT id,type,name,config_json AS config,status,updated_at AS expectedUpdatedAt FROM provider_gateway WHERE id=? AND organization_id=?"
|
|
80403
|
-
).bind(gatewayId, organizationId).first();
|
|
80404
|
-
if (!gatewaySnapshot)
|
|
80405
|
-
throw new GatewayError(
|
|
80406
|
-
404,
|
|
80407
|
-
"not_found",
|
|
80408
|
-
"Provider gateway was not found in this account"
|
|
80409
|
-
);
|
|
80410
|
-
value = JSON.stringify({
|
|
80411
|
-
...captured,
|
|
80412
|
-
gatewaySnapshot,
|
|
80413
|
-
expectedGatewayUpdatedAt: gatewaySnapshot.expectedUpdatedAt
|
|
80414
|
-
});
|
|
80415
|
-
}
|
|
80416
|
-
}
|
|
80417
|
-
await enforceEndpointRateLimit(
|
|
80418
|
-
c.env,
|
|
80419
|
-
`operation:${organizationId ?? c.req.header("cf-connecting-ip") ?? "local"}`,
|
|
80420
|
-
10,
|
|
80421
|
-
6e4
|
|
80422
|
-
);
|
|
80423
|
-
const now2 = Date.now();
|
|
80424
|
-
await c.env.DB.prepare(
|
|
80425
|
-
`INSERT OR IGNORE INTO mgmt_handoff(id,kind,request_json,organization_id,initiating_user_id,initiating_credential_id,submission_proof_hash,poll_proof_hash,human_code_hash,expires_at,created_at,updated_at)
|
|
80426
|
-
SELECT ?,?,?,?,?,?,?,?,?,?,?,? WHERE (SELECT COUNT(*) FROM mgmt_handoff WHERE organization_id = ? AND consumed_at IS NULL AND expires_at>?) < 10`
|
|
80427
|
-
).bind(
|
|
80428
|
-
id,
|
|
80429
|
-
input.kind,
|
|
80430
|
-
value,
|
|
80431
|
-
organizationId,
|
|
80432
|
-
userId,
|
|
80433
|
-
credentialId,
|
|
80434
|
-
await digest2(submissionToken),
|
|
80435
|
-
pollHash,
|
|
80436
|
-
await digest2(humanCode),
|
|
80437
|
-
now2 + TTL,
|
|
80438
|
-
now2,
|
|
80439
|
-
now2,
|
|
80440
|
-
organizationId,
|
|
80441
|
-
now2
|
|
80442
|
-
).run();
|
|
80443
|
-
row = await c.env.DB.prepare(
|
|
80444
|
-
"SELECT * FROM mgmt_handoff WHERE id=?"
|
|
80445
|
-
).bind(id).first();
|
|
80446
|
-
if (!row)
|
|
80447
|
-
throw new GatewayError(
|
|
80448
|
-
429,
|
|
80449
|
-
"rate_limited",
|
|
80450
|
-
"Too many pending operations"
|
|
80451
|
-
);
|
|
80452
|
-
}
|
|
80453
|
-
return c.json({
|
|
80454
|
-
id,
|
|
80455
|
-
url: `${meta3.consoleOrigin}/v1/cli/browser/${encodeURIComponent(id)}#${submissionToken}`,
|
|
80456
|
-
expiresAt: new Date(row.expires_at).toISOString(),
|
|
80457
|
-
state: row.consumed_at ? "completed" : row.expires_at <= Date.now() ? "expired" : "pending",
|
|
80458
|
-
...input.kind === "claim" ? { humanCode } : {},
|
|
80459
|
-
deployment: meta3
|
|
80460
|
-
});
|
|
80461
|
-
}
|
|
80462
|
-
__name(createOperation, "createOperation");
|
|
80463
|
-
async function pollOperation(c) {
|
|
80464
|
-
const row = await challenge(c);
|
|
80465
|
-
const token = c.req.header("authorization")?.replace(/^Bearer /, "");
|
|
80466
|
-
if (!await proofMatches(token, row.poll_proof_hash))
|
|
80467
|
-
throw new GatewayError(403, "forbidden", "Invalid polling proof");
|
|
80468
|
-
const base = {
|
|
80469
|
-
id: row.id,
|
|
80470
|
-
deployment: deployment(c),
|
|
80471
|
-
expiresAt: new Date(row.expires_at).toISOString()
|
|
80472
|
-
};
|
|
80473
|
-
if (row.expires_at <= Date.now())
|
|
80474
|
-
return c.json({ ...base, state: "expired" });
|
|
80475
|
-
if (!row.consumed_at) return c.json({ ...base, state: "pending" });
|
|
80476
|
-
const result = row.outcome ? JSON.parse(row.outcome) : {};
|
|
80477
|
-
const accountId = result.accountId ?? row.organization_id;
|
|
80478
|
-
const account = accountId ? await accountLifecycle(c.env, accountId) : null;
|
|
80479
|
-
return c.json({
|
|
80480
|
-
...base,
|
|
80481
|
-
state: "completed",
|
|
80482
|
-
result,
|
|
80483
|
-
account
|
|
80484
|
-
});
|
|
80485
|
-
}
|
|
80486
|
-
__name(pollOperation, "pollOperation");
|
|
80487
|
-
|
|
80488
80514
|
// src/routes/cli/identity-handoff.ts
|
|
80489
80515
|
init_errors();
|
|
80490
|
-
|
|
80516
|
+
function claimRefusal(state, organizationId) {
|
|
80517
|
+
if (state.assurance !== "interactive" || state.user?.kind !== "human" || !state.actor?.credentialId)
|
|
80518
|
+
return "session_required";
|
|
80519
|
+
if (state.memberships.some((member) => member.organization.id !== organizationId))
|
|
80520
|
+
return "account_exists";
|
|
80521
|
+
return null;
|
|
80522
|
+
}
|
|
80523
|
+
__name(claimRefusal, "claimRefusal");
|
|
80524
|
+
async function completeIdentity(c, row) {
|
|
80491
80525
|
if (row.kind !== "claim")
|
|
80492
80526
|
throw new GatewayError(400, "invalid_request", "Unsupported identity handoff");
|
|
80493
80527
|
const state = await authState(c, true);
|
|
80494
|
-
|
|
80528
|
+
const refusal = claimRefusal(state, row.organization_id);
|
|
80529
|
+
const approver = state.user;
|
|
80530
|
+
if (refusal === "session_required" || !approver)
|
|
80495
80531
|
throw new GatewayError(
|
|
80496
80532
|
401,
|
|
80497
80533
|
"session_required",
|
|
80498
80534
|
"Sign in as a person before approving this request"
|
|
80499
80535
|
);
|
|
80536
|
+
if (refusal === "account_exists")
|
|
80537
|
+
throw new GatewayError(
|
|
80538
|
+
403,
|
|
80539
|
+
"account_exists",
|
|
80540
|
+
"This sign-in already has an account; sign out and create a new sign-in to claim this one"
|
|
80541
|
+
);
|
|
80500
80542
|
const target2 = row.organization_id;
|
|
80501
80543
|
await assertAccountAccess(c.env, target2, "claim");
|
|
80502
80544
|
try {
|
|
@@ -80508,7 +80550,11 @@ async function completeIdentity(c, row, allowServiceAccess) {
|
|
|
80508
80550
|
provisioning: {
|
|
80509
80551
|
userId: row.initiating_user_id,
|
|
80510
80552
|
credentialId: row.initiating_credential_id,
|
|
80511
|
-
|
|
80553
|
+
// A claim never retires the CLI that asked for it: the person approving
|
|
80554
|
+
// is at their terminal mid-command, and an approval that logged them
|
|
80555
|
+
// out of it would be a worse answer than anything it could protect
|
|
80556
|
+
// against. Retiring that access is the console's job, afterwards.
|
|
80557
|
+
revokeAccess: false
|
|
80512
80558
|
}
|
|
80513
80559
|
});
|
|
80514
80560
|
} catch (error52) {
|
|
@@ -80521,11 +80567,7 @@ async function completeIdentity(c, row, allowServiceAccess) {
|
|
|
80521
80567
|
WHERE id=? AND kind='claim' AND consumed_at IS NULL AND expires_at>?`
|
|
80522
80568
|
).bind(
|
|
80523
80569
|
now2,
|
|
80524
|
-
JSON.stringify({
|
|
80525
|
-
accountId: target2,
|
|
80526
|
-
accessGranted: allowServiceAccess,
|
|
80527
|
-
approvedBy: state.user.id
|
|
80528
|
-
}),
|
|
80570
|
+
JSON.stringify({ accountId: target2, approvedBy: approver.id }),
|
|
80529
80571
|
now2,
|
|
80530
80572
|
row.id,
|
|
80531
80573
|
now2
|
|
@@ -80551,7 +80593,11 @@ async function completeIdentity(c, row, allowServiceAccess) {
|
|
|
80551
80593
|
__name(completeIdentity, "completeIdentity");
|
|
80552
80594
|
|
|
80553
80595
|
// src/routes/cli/browser.ts
|
|
80554
|
-
|
|
80596
|
+
function refusalFor(row, state) {
|
|
80597
|
+
return row.kind === "claim" ? claimRefusal(state, row.organization_id) : null;
|
|
80598
|
+
}
|
|
80599
|
+
__name(refusalFor, "refusalFor");
|
|
80600
|
+
async function verifiedSubmission(c) {
|
|
80555
80601
|
const meta3 = deployment(c);
|
|
80556
80602
|
if (new URL(c.req.url).origin !== meta3.consoleOrigin)
|
|
80557
80603
|
throw new GatewayError(404, "not_found", "Page was not found");
|
|
@@ -80571,15 +80617,6 @@ async function verifiedSubmission(c, codeRequired = false) {
|
|
|
80571
80617
|
throw new GatewayError(410, "invalid_request", "Operation has expired");
|
|
80572
80618
|
if (!await proofMatches(input.submissionToken, row.submission_proof_hash))
|
|
80573
80619
|
throw new GatewayError(403, "forbidden", "Invalid submission proof");
|
|
80574
|
-
if (codeRequired && !await proofMatches(
|
|
80575
|
-
input.humanCode?.trim().toUpperCase(),
|
|
80576
|
-
row.human_code_hash
|
|
80577
|
-
))
|
|
80578
|
-
throw new GatewayError(
|
|
80579
|
-
403,
|
|
80580
|
-
"forbidden",
|
|
80581
|
-
"Enter the separate code shown in your CLI"
|
|
80582
|
-
);
|
|
80583
80620
|
await assertAccountAccess(c.env, row.organization_id, row.kind === "claim" ? "claim" : "read");
|
|
80584
80621
|
return { input, row };
|
|
80585
80622
|
}
|
|
@@ -80598,14 +80635,17 @@ async function browserDetails(c) {
|
|
|
80598
80635
|
kind: row.kind,
|
|
80599
80636
|
payload: visiblePayload,
|
|
80600
80637
|
account: await accountLifecycle(c.env, row.organization_id),
|
|
80601
|
-
|
|
80638
|
+
// Named rather than reduced to a flag: the page shows who is about to
|
|
80639
|
+
// approve, so a person who is signed in as the wrong human can see it.
|
|
80640
|
+
viewer: state.assurance === "interactive" && state.user?.kind === "human" ? { name: state.user.name, email: state.user.email } : null,
|
|
80641
|
+
blockedBy: refusalFor(row, state),
|
|
80602
80642
|
googleEnabled: googleAuthEnabled(c.env),
|
|
80603
80643
|
expiresAt: new Date(row.expires_at).toISOString()
|
|
80604
80644
|
});
|
|
80605
80645
|
}
|
|
80606
80646
|
__name(browserDetails, "browserDetails");
|
|
80607
80647
|
async function browserRegister(c) {
|
|
80608
|
-
const { row, input } = await verifiedSubmission(c
|
|
80648
|
+
const { row, input } = await verifiedSubmission(c);
|
|
80609
80649
|
if (row.kind !== "claim" || row.consumed_at)
|
|
80610
80650
|
throw new GatewayError(
|
|
80611
80651
|
403,
|
|
@@ -80631,15 +80671,6 @@ async function browserRegister(c) {
|
|
|
80631
80671
|
__name(browserRegister, "browserRegister");
|
|
80632
80672
|
async function browserSubmit(c) {
|
|
80633
80673
|
const { row, input } = await verifiedSubmission(c);
|
|
80634
|
-
if (row.kind === "claim" && !await proofMatches(
|
|
80635
|
-
input.humanCode?.trim().toUpperCase(),
|
|
80636
|
-
row.human_code_hash
|
|
80637
|
-
))
|
|
80638
|
-
throw new GatewayError(
|
|
80639
|
-
403,
|
|
80640
|
-
"forbidden",
|
|
80641
|
-
"Enter the separate code shown in your CLI"
|
|
80642
|
-
);
|
|
80643
80674
|
if (input.approve !== true)
|
|
80644
80675
|
throw new GatewayError(
|
|
80645
80676
|
400,
|
|
@@ -80647,13 +80678,7 @@ async function browserSubmit(c) {
|
|
|
80647
80678
|
"Explicit approval is required"
|
|
80648
80679
|
);
|
|
80649
80680
|
if (row.kind === "claim") {
|
|
80650
|
-
|
|
80651
|
-
throw new GatewayError(
|
|
80652
|
-
400,
|
|
80653
|
-
"invalid_request",
|
|
80654
|
-
"Choose whether to allow ongoing CLI access"
|
|
80655
|
-
);
|
|
80656
|
-
await completeIdentity(c, row, input.allowServiceAccess);
|
|
80681
|
+
await completeIdentity(c, row);
|
|
80657
80682
|
} else {
|
|
80658
80683
|
await completeProviderSubmission(c, row, input.secret);
|
|
80659
80684
|
}
|
|
@@ -80663,30 +80688,6 @@ async function browserSubmit(c) {
|
|
|
80663
80688
|
});
|
|
80664
80689
|
}
|
|
80665
80690
|
__name(browserSubmit, "browserSubmit");
|
|
80666
|
-
function browserPage(c) {
|
|
80667
|
-
const meta3 = deployment(c);
|
|
80668
|
-
if (new URL(c.req.url).origin !== meta3.consoleOrigin)
|
|
80669
|
-
throw new GatewayError(404, "not_found", "Page was not found");
|
|
80670
|
-
const nonce = crypto.randomUUID().replaceAll("-", "");
|
|
80671
|
-
c.header(
|
|
80672
|
-
"Content-Security-Policy",
|
|
80673
|
-
`default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`
|
|
80674
|
-
);
|
|
80675
|
-
c.header("Referrer-Policy", "no-referrer");
|
|
80676
|
-
c.header("Cache-Control", "no-store");
|
|
80677
|
-
c.header("X-Content-Type-Options", "nosniff");
|
|
80678
|
-
return c.html(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Approve CLI request \xB7 App AI Gateway</title>
|
|
80679
|
-
<style nonce="${nonce}">:root{color-scheme:light;font-family:Georgia,serif;color:#17251e;background:#f3f3eb}body{max-width:640px;margin:7vh auto;padding:24px}header{font:13px ui-monospace,monospace;letter-spacing:.12em;color:#48634c}h1{font-weight:400;font-size:40px;line-height:1.1;margin:20px 0}section{background:#fff;padding:28px;border:1px solid #d4dacf;border-radius:8px;margin:20px 0}label{display:block;margin:18px 0 6px}input,select,button{font:16px ui-sans-serif,sans-serif;border:1px solid #9aa89c;border-radius:5px;padding:12px;box-sizing:border-box;width:100%}button{background:#244e38;color:white;margin-top:22px;cursor:pointer}button:disabled{opacity:.5;cursor:wait}input[type=checkbox]{width:auto}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px}#status{font-family:ui-sans-serif,sans-serif;line-height:1.5}small{font-family:ui-sans-serif,sans-serif;line-height:1.5}</style>
|
|
80680
|
-
<header>APP AI GATEWAY / CLI APPROVAL</header><h1>Review the request.</h1><p>Compare the account and action below with your CLI before approving.</p><section><pre id="details">Loading protected request\u2026</pre><form id="signin" hidden><h2>Sign in</h2><label for="email">Email</label><input id="email" type="email" autocomplete="username"><label for="password">Password</label><input id="password" type="password" autocomplete="current-password"><button>Sign in</button><div id="registration" hidden><label for="name">Name for your account</label><input id="name" autocomplete="name"><label for="registrationCode">Separate claim code shown in your CLI</label><input id="registrationCode" autocomplete="off"><button type="button" id="register">Create your login</button></div><button type="button" id="google" hidden>Continue with Google</button></form><form id="approve" hidden><label for="secret" id="secretLabel" hidden>Provider credential</label><input id="secret" type="password" autocomplete="off" hidden><label for="code" id="codeLabel">Separate code shown in your CLI</label><input id="code" autocomplete="off" spellcheck="false" required><label><input id="confirm" type="checkbox" required> I approve this action for the account shown above.</label><label id="ongoingLabel"><input id="ongoing" type="checkbox" checked> Keep this service identity and its role-based management access.</label><button>Approve request</button></form></section><p id="status" role="status"></p><small>This page never displays management credentials. Provider credentials are submitted directly to your gateway.</small>
|
|
80681
|
-
<script nonce="${nonce}">let saved=null;try{saved=JSON.parse(sessionStorage.getItem(location.pathname)||'null')}catch{}if(saved&&saved.expiresAt<=Date.now()){sessionStorage.removeItem(location.pathname);saved=null}const token=location.hash.slice(1)||saved?.token||'';history.replaceState(null,'',location.pathname);const base=location.pathname;const details=document.getElementById('details'),status=document.getElementById('status');let current;
|
|
80682
|
-
async function call(path,body){const r=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});const data=await r.json();if(!r.ok)throw Error(data.error?.message||data.message||'Request failed');return data}
|
|
80683
|
-
async function load(){current=await call(base+'/details',{submissionToken:token});sessionStorage.setItem(base,JSON.stringify({token,expiresAt:Date.parse(current.expiresAt)}));details.textContent=JSON.stringify({action:current.kind,account:current.account,configuration:current.payload,expiresAt:current.expiresAt},null,2);const identity=current.kind==='claim';document.getElementById('signin').hidden=!identity||current.signedIn;document.getElementById('registration').hidden=!identity;document.getElementById('google').hidden=!current.googleEnabled;document.getElementById('approve').hidden=identity&&!current.signedIn;const needsSecret=!identity&&!(current.kind==='provider.add'&¤t.payload.providerGatewayId);document.getElementById('secret').hidden=!needsSecret;document.getElementById('secret').required=needsSecret;document.getElementById('secretLabel').hidden=!needsSecret;document.getElementById('ongoingLabel').hidden=!identity;document.getElementById('code').required=identity;document.getElementById('code').hidden=!identity;document.getElementById('codeLabel').hidden=!identity;}
|
|
80684
|
-
document.getElementById('signin').onsubmit=async e=>{e.preventDefault();try{await call('/v1/auth/sign-in/email',{email:document.getElementById('email').value,password:document.getElementById('password').value});document.getElementById('password').value='';await load()}catch(e){status.textContent=e.message}};
|
|
80685
|
-
document.getElementById('register').onclick=async()=>{try{await call(base+'/register',{submissionToken:token,humanCode:document.getElementById('registrationCode').value,email:document.getElementById('email').value,password:document.getElementById('password').value,name:document.getElementById('name').value});document.getElementById('password').value='';await load()}catch(e){status.textContent=e.message}};
|
|
80686
|
-
document.getElementById('google').onclick=async()=>{try{const result=await call(current.kind==='claim'?base+'/google':'/v1/auth/sign-in/social',current.kind==='claim'?{submissionToken:token,humanCode:document.getElementById('registrationCode').value}:{provider:'google',callbackURL:location.href});location.href=result.url}catch(e){status.textContent=e.message}};
|
|
80687
|
-
document.getElementById('approve').onsubmit=async e=>{e.preventDefault();const b=e.target.querySelector('button');b.disabled=true;try{const value={submissionToken:token,humanCode:document.getElementById('code').value,approve:true,allowServiceAccess:document.getElementById('ongoing').checked};if(!document.getElementById('secret').hidden)value.secret=document.getElementById('secret').value;await call(base+'/submit',value);document.getElementById('secret').value='';status.textContent='Approved. Return to your CLI.';sessionStorage.removeItem(base);e.target.hidden=true}catch(e){status.textContent=e.message;b.disabled=false}};load().catch(e=>status.textContent=e.message);<\/script></html>`);
|
|
80688
|
-
}
|
|
80689
|
-
__name(browserPage, "browserPage");
|
|
80690
80691
|
|
|
80691
80692
|
// src/routes/cli/oauth.ts
|
|
80692
80693
|
var CLAIM_OAUTH_COOKIE = "cli_claim_oauth";
|
|
@@ -80710,7 +80711,7 @@ async function claimOAuthAuthorized(env2, request) {
|
|
|
80710
80711
|
}
|
|
80711
80712
|
__name(claimOAuthAuthorized, "claimOAuthAuthorized");
|
|
80712
80713
|
async function browserGoogle(c) {
|
|
80713
|
-
const { row } = await verifiedSubmission(c
|
|
80714
|
+
const { row } = await verifiedSubmission(c);
|
|
80714
80715
|
if (row.kind !== "claim" || row.consumed_at)
|
|
80715
80716
|
throw new GatewayError(403, "forbidden", "Google registration requires a pending claim");
|
|
80716
80717
|
const meta3 = deployment(c);
|
|
@@ -80719,7 +80720,7 @@ async function browserGoogle(c) {
|
|
|
80719
80720
|
const rawResult = await createClaimRegistrationAuth(c.env, c.req.url).auth.api.signInSocial({
|
|
80720
80721
|
body: {
|
|
80721
80722
|
provider: "google",
|
|
80722
|
-
callbackURL: `${meta3.consoleOrigin}
|
|
80723
|
+
callbackURL: `${meta3.consoleOrigin}${browserPath(row.id)}`
|
|
80723
80724
|
},
|
|
80724
80725
|
headers: c.req.raw.headers,
|
|
80725
80726
|
asResponse: true
|
|
@@ -80941,7 +80942,7 @@ GROUP BY app_id`).bind(organizationId, month).all();
|
|
|
80941
80942
|
__name(organizationMonthUsage, "organizationMonthUsage");
|
|
80942
80943
|
|
|
80943
80944
|
// package.json
|
|
80944
|
-
var version4 = "0.1.
|
|
80945
|
+
var version4 = "0.1.5";
|
|
80945
80946
|
|
|
80946
80947
|
// src/core/version.ts
|
|
80947
80948
|
var SERVER_VERSION = version4;
|
|
@@ -80984,7 +80985,6 @@ cliRoutes.get("/capabilities", (c) => {
|
|
|
80984
80985
|
cliRoutes.post("/bootstrap", bootstrap);
|
|
80985
80986
|
cliRoutes.post("/operations", createOperation);
|
|
80986
80987
|
cliRoutes.get("/operations/:id", pollOperation);
|
|
80987
|
-
cliRoutes.get("/browser/:id", browserPage);
|
|
80988
80988
|
cliRoutes.post("/browser/:id/details", browserDetails);
|
|
80989
80989
|
cliRoutes.post("/browser/:id/submit", browserSubmit);
|
|
80990
80990
|
cliRoutes.post("/browser/:id/register", browserRegister);
|