@domino-sdk/relay-runtime 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1097 -194
- package/package.json +1 -1
- package/wrangler.jsonc +14 -0
package/dist/index.js
CHANGED
|
@@ -14833,7 +14833,40 @@ var init_chunk_C2KOMNLG = __esm({
|
|
|
14833
14833
|
}
|
|
14834
14834
|
});
|
|
14835
14835
|
|
|
14836
|
-
// packages/sdk/dist/chunk-
|
|
14836
|
+
// packages/sdk/dist/chunk-6IXQRMGW.js
|
|
14837
|
+
var authScopeSchema, principalSchema, sessionSchema, exchangeSchema, exchangeResultSchema;
|
|
14838
|
+
var init_chunk_6IXQRMGW = __esm({
|
|
14839
|
+
"packages/sdk/dist/chunk-6IXQRMGW.js"() {
|
|
14840
|
+
"use strict";
|
|
14841
|
+
init_chunk_C2KOMNLG();
|
|
14842
|
+
init_zod();
|
|
14843
|
+
authScopeSchema = external_exports.object({
|
|
14844
|
+
organization: identifier,
|
|
14845
|
+
project: identifier,
|
|
14846
|
+
environment: external_exports.enum(["test", "live"])
|
|
14847
|
+
}).strict();
|
|
14848
|
+
principalSchema = external_exports.discriminatedUnion("kind", [
|
|
14849
|
+
external_exports.object({ kind: external_exports.literal("member"), member: identifier }).strict(),
|
|
14850
|
+
external_exports.object({ kind: external_exports.literal("staff"), subject: identifier }).strict(),
|
|
14851
|
+
external_exports.object({ kind: external_exports.literal("operator"), subject: identifier }).strict()
|
|
14852
|
+
]);
|
|
14853
|
+
sessionSchema = authScopeSchema.extend({
|
|
14854
|
+
id: identifier,
|
|
14855
|
+
principal: principalSchema,
|
|
14856
|
+
expiresAt: external_exports.number()
|
|
14857
|
+
});
|
|
14858
|
+
exchangeSchema = authScopeSchema.extend({
|
|
14859
|
+
identity: external_exports.object({ issuer: identifier, subject: external_exports.string().min(1).max(200) }).strict(),
|
|
14860
|
+
role: external_exports.enum(["member", "staff", "operator"])
|
|
14861
|
+
}).strict();
|
|
14862
|
+
exchangeResultSchema = external_exports.object({
|
|
14863
|
+
token: external_exports.string(),
|
|
14864
|
+
session: sessionSchema
|
|
14865
|
+
});
|
|
14866
|
+
}
|
|
14867
|
+
});
|
|
14868
|
+
|
|
14869
|
+
// packages/sdk/dist/chunk-L757OYXZ.js
|
|
14837
14870
|
function questInteraction(trigger) {
|
|
14838
14871
|
return trigger.kind === "automatic" ? "automatic" : trigger.actor === "staff" ? "staff" : trigger.input === "none" ? "claim" : trigger.input;
|
|
14839
14872
|
}
|
|
@@ -15439,6 +15472,7 @@ function createRelayClient(options) {
|
|
|
15439
15472
|
signOut: () => changeSession(
|
|
15440
15473
|
() => request("/v1/auth/logout", external_exports.object({ ok: external_exports.boolean() }), json3({}))
|
|
15441
15474
|
),
|
|
15475
|
+
/** Local development only. Existing hosted apps use createParticipantBackend from /portal-proxy. */
|
|
15442
15476
|
exchange: (input) => request("/v1/auth/exchange", exchangeResultSchema, json3(input))
|
|
15443
15477
|
},
|
|
15444
15478
|
me: {
|
|
@@ -15543,10 +15577,11 @@ function createRelayClient(options) {
|
|
|
15543
15577
|
decide: (id2, input) => request(`/v1/attempts/${id2}/review`, attemptSchema, json3(input))
|
|
15544
15578
|
};
|
|
15545
15579
|
}
|
|
15546
|
-
var interactionSchema, questTypeVersionSchema, collectionSlotSchema, questCollectionSchema, questInstanceSchema, questDraftChangesSchema, draftBase, questDraftSchema, questCatalogSchema, catalogDeploymentSchema, catalogDeploymentPreviewSchema, saveQuestDraftSchema, publishQuestDraftSchema, questViewSchema, collectionViewSchema, questExperienceSchema, questPublicationPreviewSchema, RelayError, common, claimInputSchema, draftSchema, recordSchema, QuestUpdatedError,
|
|
15547
|
-
var
|
|
15548
|
-
"packages/sdk/dist/chunk-
|
|
15580
|
+
var interactionSchema, questTypeVersionSchema, collectionSlotSchema, questCollectionSchema, questInstanceSchema, questDraftChangesSchema, draftBase, questDraftSchema, questCatalogSchema, catalogDeploymentSchema, catalogDeploymentPreviewSchema, saveQuestDraftSchema, publishQuestDraftSchema, questViewSchema, collectionViewSchema, questExperienceSchema, questPublicationPreviewSchema, RelayError, common, claimInputSchema, draftSchema, recordSchema, QuestUpdatedError, solanaAddressSchema, walletConnectionSchema, walletChallengeInputSchema, walletChallengeSchema, walletProofSchema, solanaBalanceSchema, collectionCodeSchema, collectionPassSchema, collectionResolveSchema, collectionBeginSchema, collectionPreviewSchema, projectDeploymentSchema, deploymentPreviewSchema, publishDeploymentSchema, deploymentResultSchema, identityIntegrationInputSchema, identityIntegrationSchema, identityIntegrationSecretSchema, participantExchangeSchema;
|
|
15581
|
+
var init_chunk_L757OYXZ = __esm({
|
|
15582
|
+
"packages/sdk/dist/chunk-L757OYXZ.js"() {
|
|
15549
15583
|
"use strict";
|
|
15584
|
+
init_chunk_6IXQRMGW();
|
|
15550
15585
|
init_chunk_C2KOMNLG();
|
|
15551
15586
|
init_zod();
|
|
15552
15587
|
init_zod();
|
|
@@ -15718,29 +15753,6 @@ var init_chunk_NC3UAP6T = __esm({
|
|
|
15718
15753
|
}
|
|
15719
15754
|
draft;
|
|
15720
15755
|
};
|
|
15721
|
-
authScopeSchema = external_exports.object({
|
|
15722
|
-
organization: identifier,
|
|
15723
|
-
project: identifier,
|
|
15724
|
-
environment: external_exports.enum(["test", "live"])
|
|
15725
|
-
}).strict();
|
|
15726
|
-
principalSchema = external_exports.discriminatedUnion("kind", [
|
|
15727
|
-
external_exports.object({ kind: external_exports.literal("member"), member: identifier }).strict(),
|
|
15728
|
-
external_exports.object({ kind: external_exports.literal("staff"), subject: identifier }).strict(),
|
|
15729
|
-
external_exports.object({ kind: external_exports.literal("operator"), subject: identifier }).strict()
|
|
15730
|
-
]);
|
|
15731
|
-
sessionSchema = authScopeSchema.extend({
|
|
15732
|
-
id: identifier,
|
|
15733
|
-
principal: principalSchema,
|
|
15734
|
-
expiresAt: external_exports.number()
|
|
15735
|
-
});
|
|
15736
|
-
exchangeSchema = authScopeSchema.extend({
|
|
15737
|
-
identity: external_exports.object({ issuer: identifier, subject: external_exports.string().min(1).max(200) }).strict(),
|
|
15738
|
-
role: external_exports.enum(["member", "staff", "operator"])
|
|
15739
|
-
}).strict();
|
|
15740
|
-
exchangeResultSchema = external_exports.object({
|
|
15741
|
-
token: external_exports.string(),
|
|
15742
|
-
session: sessionSchema
|
|
15743
|
-
});
|
|
15744
15756
|
solanaAddressSchema = external_exports.string().min(32).max(44).regex(/^[1-9A-HJ-NP-Za-km-z]+$/);
|
|
15745
15757
|
walletConnectionSchema = external_exports.object({
|
|
15746
15758
|
chain: external_exports.literal("solana"),
|
|
@@ -15803,6 +15815,28 @@ var init_chunk_NC3UAP6T = __esm({
|
|
|
15803
15815
|
deploymentResultSchema = deploymentPreviewSchema.extend({
|
|
15804
15816
|
revision: external_exports.number().int().positive()
|
|
15805
15817
|
});
|
|
15818
|
+
identityIntegrationInputSchema = external_exports.object({
|
|
15819
|
+
name: external_exports.string().trim().min(1).max(80),
|
|
15820
|
+
origin: external_exports.string().url().refine((value) => {
|
|
15821
|
+
const url2 = new URL(value);
|
|
15822
|
+
return url2.protocol === "https:" && url2.origin === value;
|
|
15823
|
+
}, "Use an exact HTTPS application origin")
|
|
15824
|
+
}).strict();
|
|
15825
|
+
identityIntegrationSchema = authScopeSchema.extend({
|
|
15826
|
+
id: external_exports.string().uuid(),
|
|
15827
|
+
name: external_exports.string(),
|
|
15828
|
+
origin: external_exports.string(),
|
|
15829
|
+
expiresAt: external_exports.number()
|
|
15830
|
+
});
|
|
15831
|
+
identityIntegrationSecretSchema = identityIntegrationSchema.extend(
|
|
15832
|
+
{
|
|
15833
|
+
token: external_exports.string()
|
|
15834
|
+
}
|
|
15835
|
+
);
|
|
15836
|
+
participantExchangeSchema = external_exports.object({
|
|
15837
|
+
subject: external_exports.string().min(1).max(200),
|
|
15838
|
+
previousToken: external_exports.string().max(200).optional()
|
|
15839
|
+
}).strict();
|
|
15806
15840
|
}
|
|
15807
15841
|
});
|
|
15808
15842
|
|
|
@@ -15839,12 +15873,16 @@ __export(dist_exports, {
|
|
|
15839
15873
|
exchangeResultSchema: () => exchangeResultSchema,
|
|
15840
15874
|
exchangeSchema: () => exchangeSchema,
|
|
15841
15875
|
identifier: () => identifier,
|
|
15876
|
+
identityIntegrationInputSchema: () => identityIntegrationInputSchema,
|
|
15877
|
+
identityIntegrationSchema: () => identityIntegrationSchema,
|
|
15878
|
+
identityIntegrationSecretSchema: () => identityIntegrationSecretSchema,
|
|
15842
15879
|
interactionSchema: () => interactionSchema,
|
|
15843
15880
|
mediaUrlSchema: () => mediaUrlSchema,
|
|
15844
15881
|
memberListSchema: () => memberListSchema,
|
|
15845
15882
|
memberPageSchema: () => memberPageSchema,
|
|
15846
15883
|
memberProgressSchema: () => memberProgressSchema,
|
|
15847
15884
|
parseSettings: () => parseSettings,
|
|
15885
|
+
participantExchangeSchema: () => participantExchangeSchema,
|
|
15848
15886
|
photoResultSchema: () => photoResultSchema,
|
|
15849
15887
|
pickupAllocationSchema: () => pickupAllocationSchema,
|
|
15850
15888
|
pickupAvailabilitySchema: () => pickupAvailabilitySchema,
|
|
@@ -15914,11 +15952,512 @@ __export(dist_exports, {
|
|
|
15914
15952
|
var init_dist = __esm({
|
|
15915
15953
|
"packages/sdk/dist/index.js"() {
|
|
15916
15954
|
"use strict";
|
|
15917
|
-
|
|
15955
|
+
init_chunk_L757OYXZ();
|
|
15956
|
+
init_chunk_6IXQRMGW();
|
|
15918
15957
|
init_chunk_C2KOMNLG();
|
|
15919
15958
|
}
|
|
15920
15959
|
});
|
|
15921
15960
|
|
|
15961
|
+
// apps/api/src/identity-integration.ts
|
|
15962
|
+
init_zod();
|
|
15963
|
+
init_dist();
|
|
15964
|
+
|
|
15965
|
+
// apps/api/src/access-error.ts
|
|
15966
|
+
var AccessError = class extends Error {
|
|
15967
|
+
constructor(message2, status = 403) {
|
|
15968
|
+
super(message2);
|
|
15969
|
+
this.status = status;
|
|
15970
|
+
}
|
|
15971
|
+
};
|
|
15972
|
+
|
|
15973
|
+
// apps/api/src/auth-utils.ts
|
|
15974
|
+
function cookie(request, name) {
|
|
15975
|
+
return request.headers.get("Cookie")?.split(";").map((value) => value.trim()).find((value) => value.startsWith(name + "="))?.slice(name.length + 1) ?? "";
|
|
15976
|
+
}
|
|
15977
|
+
function bindingCookie(value, secure, name = "relay_auth_flow") {
|
|
15978
|
+
return `${name}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${value ? 600 : 0}${secure ? "; Secure" : ""}`;
|
|
15979
|
+
}
|
|
15980
|
+
async function hash2(value) {
|
|
15981
|
+
const bytes = await crypto.subtle.digest(
|
|
15982
|
+
"SHA-256",
|
|
15983
|
+
new TextEncoder().encode(value)
|
|
15984
|
+
);
|
|
15985
|
+
return Array.from(
|
|
15986
|
+
new Uint8Array(bytes),
|
|
15987
|
+
(b) => b.toString(16).padStart(2, "0")
|
|
15988
|
+
).join("");
|
|
15989
|
+
}
|
|
15990
|
+
function sessionToken(request) {
|
|
15991
|
+
const bearer = request.headers.get("Authorization");
|
|
15992
|
+
if (bearer) return bearer.startsWith("Bearer ") ? bearer.slice(7) : "";
|
|
15993
|
+
return request.headers.get("Cookie")?.split(";").map((x) => x.trim()).find((x) => x.startsWith("relay_session="))?.slice("relay_session=".length) ?? "";
|
|
15994
|
+
}
|
|
15995
|
+
function sessionCookie(token, secure) {
|
|
15996
|
+
return `relay_session=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${token ? 28800 : 0}${secure ? "; Secure" : ""}`;
|
|
15997
|
+
}
|
|
15998
|
+
function browserMutationAllowed(request, env) {
|
|
15999
|
+
const origin = request.headers.get("Origin");
|
|
16000
|
+
return request.headers.get("X-Relay-CSRF") === "1" && !!origin && (env.AUTH_ORIGINS ?? "").split(",").map((value) => value.trim()).includes(origin);
|
|
16001
|
+
}
|
|
16002
|
+
|
|
16003
|
+
// apps/api/src/identity-integration.ts
|
|
16004
|
+
async function identityIntegrationRequest(request, env, participant) {
|
|
16005
|
+
const path = new URL(request.url).pathname;
|
|
16006
|
+
const key = request.headers.get("X-Relay-Integration-Key");
|
|
16007
|
+
const exchange = path === "/v1/auth/exchange" && !(env.LOCAL_DEVELOPMENT === "true" && ["localhost", "127.0.0.1", "[::1]"].includes(
|
|
16008
|
+
new URL(request.url).hostname
|
|
16009
|
+
) && !request.headers.get("Authorization")?.startsWith("Bearer di_"));
|
|
16010
|
+
if (!exchange && !key) return null;
|
|
16011
|
+
try {
|
|
16012
|
+
const auth = env.AUTH.getByName("auth-v1");
|
|
16013
|
+
if (exchange) {
|
|
16014
|
+
if (request.method !== "POST")
|
|
16015
|
+
return Response.json(
|
|
16016
|
+
{ error: "Use POST for identity exchange" },
|
|
16017
|
+
{ status: 405 }
|
|
16018
|
+
);
|
|
16019
|
+
const result = await auth.exchangeParticipant(
|
|
16020
|
+
request.headers.get("Authorization")?.replace(/^Bearer /, "") ?? "",
|
|
16021
|
+
participantExchangeSchema.parse(await request.json())
|
|
16022
|
+
);
|
|
16023
|
+
if (result.kind === "denied")
|
|
16024
|
+
throw new AccessError(
|
|
16025
|
+
"Use a valid identity integration server key from Console Settings. CLI and management credentials cannot issue participant sessions.",
|
|
16026
|
+
401
|
|
16027
|
+
);
|
|
16028
|
+
if (result.kind === "limited")
|
|
16029
|
+
throw new AccessError(
|
|
16030
|
+
"Too many identity exchange requests. Retry shortly.",
|
|
16031
|
+
429
|
|
16032
|
+
);
|
|
16033
|
+
return Response.json(result.result);
|
|
16034
|
+
}
|
|
16035
|
+
const integration = await auth.verifyIntegration(key ?? "");
|
|
16036
|
+
if (!integration)
|
|
16037
|
+
throw new AccessError(
|
|
16038
|
+
"Identity integration key is invalid, expired, or revoked",
|
|
16039
|
+
401
|
|
16040
|
+
);
|
|
16041
|
+
if (!/^\/v1\/(?:auth\/(?:session|logout|connections)|me\/(?:progress|experience|attempts|evidence|pass|collection-pass|wallet(?:\/challenge|\/verify)?)|attempts\/[\w-]+(?:\/retry)?)$/.test(
|
|
16042
|
+
path
|
|
16043
|
+
))
|
|
16044
|
+
return Response.json(
|
|
16045
|
+
{ error: "Route is not available through an identity integration" },
|
|
16046
|
+
{ status: 404 }
|
|
16047
|
+
);
|
|
16048
|
+
if (request.headers.get("Origin") !== integration.origin)
|
|
16049
|
+
throw new AccessError(
|
|
16050
|
+
"Application origin does not match the identity integration",
|
|
16051
|
+
403
|
|
16052
|
+
);
|
|
16053
|
+
const session = await auth.integrationSession(
|
|
16054
|
+
sessionToken(request),
|
|
16055
|
+
integration.id
|
|
16056
|
+
);
|
|
16057
|
+
if (!session)
|
|
16058
|
+
throw new AccessError(
|
|
16059
|
+
"Participant session does not belong to this identity integration",
|
|
16060
|
+
401
|
|
16061
|
+
);
|
|
16062
|
+
const headers = new Headers(request.headers);
|
|
16063
|
+
headers.delete("X-Relay-Integration-Key");
|
|
16064
|
+
return participant(
|
|
16065
|
+
new Request(request, { headers }),
|
|
16066
|
+
{
|
|
16067
|
+
...env,
|
|
16068
|
+
LOCAL_DEVELOPMENT: "false",
|
|
16069
|
+
TEST_HOOKS: "false",
|
|
16070
|
+
DEMO_AUTH: "false",
|
|
16071
|
+
AUTH_EMULATORS: void 0,
|
|
16072
|
+
API_TOKEN: void 0,
|
|
16073
|
+
AUTH_ORGANIZATION: integration.organization,
|
|
16074
|
+
AUTH_PROJECT: integration.project,
|
|
16075
|
+
AUTH_ENVIRONMENT: integration.environment,
|
|
16076
|
+
AUTH_ORIGINS: integration.origin
|
|
16077
|
+
},
|
|
16078
|
+
integration.id
|
|
16079
|
+
);
|
|
16080
|
+
} catch (error48) {
|
|
16081
|
+
if (error48 instanceof external_exports.ZodError || error48 instanceof SyntaxError)
|
|
16082
|
+
return Response.json(
|
|
16083
|
+
{
|
|
16084
|
+
code: "INVALID_IDENTITY_EXCHANGE",
|
|
16085
|
+
error: "Supply only a verified subject and optional previousToken"
|
|
16086
|
+
},
|
|
16087
|
+
{ status: 400 }
|
|
16088
|
+
);
|
|
16089
|
+
if (error48 instanceof AccessError)
|
|
16090
|
+
return Response.json(
|
|
16091
|
+
{ code: "IDENTITY_INTEGRATION_ACCESS_DENIED", error: error48.message },
|
|
16092
|
+
{ status: error48.status }
|
|
16093
|
+
);
|
|
16094
|
+
throw error48;
|
|
16095
|
+
}
|
|
16096
|
+
}
|
|
16097
|
+
|
|
16098
|
+
// packages/sdk/dist/portal-proxy.js
|
|
16099
|
+
init_chunk_6IXQRMGW();
|
|
16100
|
+
init_chunk_C2KOMNLG();
|
|
16101
|
+
async function proxyParticipant(request, upstream) {
|
|
16102
|
+
const incoming = new URL(request.url);
|
|
16103
|
+
const path = incoming.pathname.slice("/relay".length);
|
|
16104
|
+
const allowed = incoming.pathname.startsWith("/relay/") && (/^\/v1\/auth\/(providers|connections|start|authorize|complete|session|logout|email\/start|email\/verify|callback\/(google|apple|x|discord))$/.test(
|
|
16105
|
+
path
|
|
16106
|
+
) || /^\/v1\/me\/(progress|experience|attempts|evidence|pass|collection-pass|wallet(?:\/challenge|\/verify)?)$/.test(
|
|
16107
|
+
path
|
|
16108
|
+
) || /^\/v1\/attempts\/[\w-]+(?:\/retry)?$/.test(path));
|
|
16109
|
+
if (!allowed || !["GET", "POST"].includes(request.method))
|
|
16110
|
+
return Response.json({ error: "Route not found" }, { status: 404 });
|
|
16111
|
+
const target = new URL(upstream.baseUrl);
|
|
16112
|
+
if (target.protocol !== "https:" || target.username || target.password || target.pathname !== "/" || target.search || target.hash)
|
|
16113
|
+
return Response.json(
|
|
16114
|
+
{ error: "Participant API is not configured" },
|
|
16115
|
+
{ status: 503 }
|
|
16116
|
+
);
|
|
16117
|
+
target.pathname = path;
|
|
16118
|
+
target.search = incoming.search;
|
|
16119
|
+
const headers = new Headers();
|
|
16120
|
+
for (const name of [
|
|
16121
|
+
"Cookie",
|
|
16122
|
+
"Origin",
|
|
16123
|
+
"Content-Type",
|
|
16124
|
+
"X-Relay-CSRF",
|
|
16125
|
+
"X-Relay-Session",
|
|
16126
|
+
"X-Relay-Upload-ID",
|
|
16127
|
+
"CF-Connecting-IP"
|
|
16128
|
+
]) {
|
|
16129
|
+
const value = request.headers.get(name);
|
|
16130
|
+
if (value) headers.set(name, value);
|
|
16131
|
+
}
|
|
16132
|
+
const response = await upstream.fetch(target, {
|
|
16133
|
+
method: request.method,
|
|
16134
|
+
headers,
|
|
16135
|
+
body: request.method === "GET" ? void 0 : await request.arrayBuffer(),
|
|
16136
|
+
redirect: "manual"
|
|
16137
|
+
});
|
|
16138
|
+
const resultHeaders = new Headers(response.headers);
|
|
16139
|
+
resultHeaders.set("Cache-Control", "no-store");
|
|
16140
|
+
return new Response(response.body, {
|
|
16141
|
+
status: response.status,
|
|
16142
|
+
headers: resultHeaders
|
|
16143
|
+
});
|
|
16144
|
+
}
|
|
16145
|
+
|
|
16146
|
+
// apps/api/src/hosted-participant.ts
|
|
16147
|
+
init_zod();
|
|
16148
|
+
function hostedParticipantEnv(env, scope, origin) {
|
|
16149
|
+
return {
|
|
16150
|
+
...env,
|
|
16151
|
+
LOCAL_DEVELOPMENT: "false",
|
|
16152
|
+
TEST_HOOKS: "false",
|
|
16153
|
+
DEMO_AUTH: "false",
|
|
16154
|
+
API_TOKEN: void 0,
|
|
16155
|
+
AUTH_EMULATORS: void 0,
|
|
16156
|
+
AUTH_ORGANIZATION: scope.organization,
|
|
16157
|
+
AUTH_PROJECT: scope.project,
|
|
16158
|
+
AUTH_ENVIRONMENT: "test",
|
|
16159
|
+
AUTH_ORIGINS: origin
|
|
16160
|
+
};
|
|
16161
|
+
}
|
|
16162
|
+
async function registeredStagingScope(origin, env) {
|
|
16163
|
+
const url2 = new URL(origin);
|
|
16164
|
+
if (url2.protocol !== "https:" || url2.origin !== origin || !env.STAGING_DOMAIN)
|
|
16165
|
+
return null;
|
|
16166
|
+
const suffix = `.${env.STAGING_DOMAIN}`;
|
|
16167
|
+
if (!url2.hostname.endsWith(suffix)) return null;
|
|
16168
|
+
const name = url2.hostname.slice(0, -suffix.length);
|
|
16169
|
+
if (!/^stage-[a-f0-9]{32}$/.test(name)) return null;
|
|
16170
|
+
const raw = name.slice(6);
|
|
16171
|
+
const id2 = `${raw.slice(0, 8)}-${raw.slice(8, 12)}-${raw.slice(12, 16)}-${raw.slice(16, 20)}-${raw.slice(20)}`;
|
|
16172
|
+
const build = await env.MANAGEMENT.getByName("management-v1").activeBuild(id2);
|
|
16173
|
+
return build?.runtime === "hosted-test" ? { organization: build.organization, project: build.project } : null;
|
|
16174
|
+
}
|
|
16175
|
+
async function callbackEnvironment(request, env) {
|
|
16176
|
+
const url2 = new URL(request.url);
|
|
16177
|
+
if (request.method !== "GET") return env;
|
|
16178
|
+
const code = url2.pathname === "/v1/auth/authorize" ? url2.searchParams.get("code") : /^\/v1\/auth\/callback\/[a-z]+$/.test(url2.pathname) ? url2.searchParams.get("state") : null;
|
|
16179
|
+
if (!code) return env;
|
|
16180
|
+
const raw = await env.AUTH.getByName("auth-v1").readFlow(code);
|
|
16181
|
+
if (!raw) return env;
|
|
16182
|
+
const flow = external_exports.object({ origin: external_exports.string().url() }).safeParse(JSON.parse(raw));
|
|
16183
|
+
if (!flow.success) return env;
|
|
16184
|
+
const scope = await registeredStagingScope(flow.data.origin, env) ?? await registeredPreviewScope(flow.data.origin, env);
|
|
16185
|
+
return scope ? hostedParticipantEnv(env, scope, flow.data.origin) : env;
|
|
16186
|
+
}
|
|
16187
|
+
|
|
16188
|
+
// apps/api/src/development-preview.ts
|
|
16189
|
+
async function registeredPreviewScope(origin, env) {
|
|
16190
|
+
const url2 = new URL(origin);
|
|
16191
|
+
if (url2.protocol !== "https:" || url2.origin !== origin || !env.PREVIEW_DOMAIN || !env.PREVIEW_SESSIONS)
|
|
16192
|
+
return null;
|
|
16193
|
+
const suffix = `.${env.PREVIEW_DOMAIN}`;
|
|
16194
|
+
if (!url2.hostname.endsWith(suffix)) return null;
|
|
16195
|
+
const name = url2.hostname.slice(0, -suffix.length);
|
|
16196
|
+
if (!/^[a-z0-9-]+-[a-f0-9]{32}$/.test(name)) return null;
|
|
16197
|
+
const scope = await env.PREVIEW_SESSIONS.getByName(name.slice(-32)).resolve();
|
|
16198
|
+
return scope?.hostname === url2.hostname ? scope : null;
|
|
16199
|
+
}
|
|
16200
|
+
async function developmentPreview(request, env, participant) {
|
|
16201
|
+
const url2 = new URL(request.url);
|
|
16202
|
+
const scope = await registeredPreviewScope(url2.origin, env);
|
|
16203
|
+
if (!scope)
|
|
16204
|
+
return new Response(
|
|
16205
|
+
'<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Preview offline</title><body style="font:18px system-ui;max-width:38rem;margin:15vh auto;padding:24px"><h1>This preview is offline</h1><p>Ask its creator to start development again, or share a staged preview that stays online.</p><button onclick="location.reload()">Try again</button></body></html>',
|
|
16206
|
+
{
|
|
16207
|
+
status: 503,
|
|
16208
|
+
headers: {
|
|
16209
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
16210
|
+
"Cache-Control": "no-store",
|
|
16211
|
+
"X-Robots-Tag": "noindex, nofollow"
|
|
16212
|
+
}
|
|
16213
|
+
}
|
|
16214
|
+
);
|
|
16215
|
+
if (url2.pathname.startsWith("/relay/"))
|
|
16216
|
+
return proxyParticipant(request, {
|
|
16217
|
+
baseUrl: "https://participant.internal",
|
|
16218
|
+
fetch: (input, init) => participant(
|
|
16219
|
+
new Request(input, init),
|
|
16220
|
+
hostedParticipantEnv(env, scope, url2.origin)
|
|
16221
|
+
)
|
|
16222
|
+
});
|
|
16223
|
+
if (url2.pathname === "/relay" || /^\/(management|__domino)(\/|$)/.test(url2.pathname))
|
|
16224
|
+
return new Response("Not found", { status: 404 });
|
|
16225
|
+
return new Response(null, {
|
|
16226
|
+
status: 204,
|
|
16227
|
+
headers: { "X-Domino-Preview-Origin": "allowed" }
|
|
16228
|
+
});
|
|
16229
|
+
}
|
|
16230
|
+
|
|
16231
|
+
// apps/api/src/preview-session.ts
|
|
16232
|
+
init_zod();
|
|
16233
|
+
import { DurableObject } from "cloudflare:workers";
|
|
16234
|
+
|
|
16235
|
+
// apps/api/src/preview-tunnel.ts
|
|
16236
|
+
init_zod();
|
|
16237
|
+
var PreviewTunnel = class {
|
|
16238
|
+
constructor(env) {
|
|
16239
|
+
this.env = env;
|
|
16240
|
+
}
|
|
16241
|
+
async call(path, method = "GET", body) {
|
|
16242
|
+
const response = await fetch(
|
|
16243
|
+
`https://api.cloudflare.com/client/v4${path}`,
|
|
16244
|
+
{
|
|
16245
|
+
method,
|
|
16246
|
+
headers: {
|
|
16247
|
+
Authorization: `Bearer ${this.env.PREVIEW_CF_TOKEN}`,
|
|
16248
|
+
"Content-Type": "application/json"
|
|
16249
|
+
},
|
|
16250
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
16251
|
+
signal: AbortSignal.timeout(1e4)
|
|
16252
|
+
}
|
|
16253
|
+
);
|
|
16254
|
+
if (response.status === 404 && method === "DELETE") return null;
|
|
16255
|
+
if (!response.ok)
|
|
16256
|
+
throw new Error(
|
|
16257
|
+
`Preview infrastructure request failed (${response.status}). Contact a Domino operator.`
|
|
16258
|
+
);
|
|
16259
|
+
const result = external_exports.object({ success: external_exports.boolean(), result: external_exports.unknown() }).parse(await response.json());
|
|
16260
|
+
if (!result.success)
|
|
16261
|
+
throw new Error(
|
|
16262
|
+
"Preview infrastructure request failed. Contact a Domino operator."
|
|
16263
|
+
);
|
|
16264
|
+
return result.result;
|
|
16265
|
+
}
|
|
16266
|
+
get base() {
|
|
16267
|
+
return `/accounts/${this.env.PREVIEW_CF_ACCOUNT}/cfd_tunnel`;
|
|
16268
|
+
}
|
|
16269
|
+
async lookup(name) {
|
|
16270
|
+
const tunnels = external_exports.array(external_exports.object({ id: external_exports.string().uuid() })).parse(
|
|
16271
|
+
await this.call(
|
|
16272
|
+
`${this.base}?name=${encodeURIComponent(name)}&is_deleted=false`
|
|
16273
|
+
)
|
|
16274
|
+
);
|
|
16275
|
+
return tunnels[0]?.id ?? null;
|
|
16276
|
+
}
|
|
16277
|
+
async ensure(name, hostname3, port) {
|
|
16278
|
+
const existing = external_exports.array(external_exports.object({ id: external_exports.string().uuid() })).parse(
|
|
16279
|
+
await this.call(
|
|
16280
|
+
`${this.base}?name=${encodeURIComponent(name)}&is_deleted=false`
|
|
16281
|
+
)
|
|
16282
|
+
);
|
|
16283
|
+
const tunnel = existing[0] ?? external_exports.object({ id: external_exports.string().uuid() }).parse(
|
|
16284
|
+
await this.call(this.base, "POST", {
|
|
16285
|
+
name,
|
|
16286
|
+
config_src: "cloudflare"
|
|
16287
|
+
})
|
|
16288
|
+
);
|
|
16289
|
+
await this.call(`${this.base}/${tunnel.id}/configurations`, "PUT", {
|
|
16290
|
+
config: {
|
|
16291
|
+
ingress: [
|
|
16292
|
+
{
|
|
16293
|
+
hostname: hostname3,
|
|
16294
|
+
service: `http://127.0.0.1:${port}`,
|
|
16295
|
+
originRequest: { httpHostHeader: hostname3 }
|
|
16296
|
+
},
|
|
16297
|
+
{ service: "http_status:404" }
|
|
16298
|
+
]
|
|
16299
|
+
}
|
|
16300
|
+
});
|
|
16301
|
+
const dnsBase = `/zones/${this.env.PREVIEW_CF_ZONE}/dns_records`;
|
|
16302
|
+
const records = external_exports.array(
|
|
16303
|
+
external_exports.object({ id: external_exports.string(), type: external_exports.string(), content: external_exports.string() })
|
|
16304
|
+
).parse(
|
|
16305
|
+
await this.call(`${dnsBase}?name=${encodeURIComponent(hostname3)}`)
|
|
16306
|
+
);
|
|
16307
|
+
const content = `${tunnel.id}.cfargotunnel.com`;
|
|
16308
|
+
if (records.some(
|
|
16309
|
+
(record2) => record2.type !== "CNAME" || record2.content !== content
|
|
16310
|
+
))
|
|
16311
|
+
throw new Error(
|
|
16312
|
+
"Preview hostname conflicts with an existing DNS record."
|
|
16313
|
+
);
|
|
16314
|
+
if (!records.length)
|
|
16315
|
+
await this.call(dnsBase, "POST", {
|
|
16316
|
+
type: "CNAME",
|
|
16317
|
+
name: hostname3,
|
|
16318
|
+
content,
|
|
16319
|
+
proxied: true,
|
|
16320
|
+
ttl: 1
|
|
16321
|
+
});
|
|
16322
|
+
return tunnel.id;
|
|
16323
|
+
}
|
|
16324
|
+
async token(id2) {
|
|
16325
|
+
return external_exports.string().min(1).parse(await this.call(`${this.base}/${id2}/token`));
|
|
16326
|
+
}
|
|
16327
|
+
async remove(id2, hostname3) {
|
|
16328
|
+
const base3 = `/zones/${this.env.PREVIEW_CF_ZONE}/dns_records`;
|
|
16329
|
+
const records = external_exports.array(
|
|
16330
|
+
external_exports.object({ id: external_exports.string(), type: external_exports.string(), content: external_exports.string() })
|
|
16331
|
+
).parse(await this.call(`${base3}?name=${encodeURIComponent(hostname3)}`));
|
|
16332
|
+
for (const record2 of records) {
|
|
16333
|
+
if (record2.type !== "CNAME" || record2.content !== `${id2}.cfargotunnel.com`)
|
|
16334
|
+
throw new Error("Preview DNS ownership changed; cleanup stopped.");
|
|
16335
|
+
await this.call(`${base3}/${record2.id}`, "DELETE");
|
|
16336
|
+
}
|
|
16337
|
+
await this.call(`${this.base}/${id2}`, "DELETE");
|
|
16338
|
+
}
|
|
16339
|
+
async revoke(id2) {
|
|
16340
|
+
const secret = btoa(
|
|
16341
|
+
String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))
|
|
16342
|
+
);
|
|
16343
|
+
await this.call(`${this.base}/${id2}`, "PATCH", { tunnel_secret: secret });
|
|
16344
|
+
await this.call(`${this.base}/${id2}/connections`, "DELETE");
|
|
16345
|
+
}
|
|
16346
|
+
};
|
|
16347
|
+
function previewConfigurationIssues(env) {
|
|
16348
|
+
return Object.entries({
|
|
16349
|
+
PREVIEW_SESSIONS: env.PREVIEW_SESSIONS,
|
|
16350
|
+
PREVIEW_DOMAIN: env.PREVIEW_DOMAIN,
|
|
16351
|
+
PREVIEW_CF_TOKEN: env.PREVIEW_CF_TOKEN,
|
|
16352
|
+
PREVIEW_CF_ACCOUNT: env.PREVIEW_CF_ACCOUNT,
|
|
16353
|
+
PREVIEW_CF_ZONE: env.PREVIEW_CF_ZONE,
|
|
16354
|
+
PREVIEW_TLS_READY: env.PREVIEW_TLS_READY === "true"
|
|
16355
|
+
}).filter(([, value]) => !value).map(([key]) => key);
|
|
16356
|
+
}
|
|
16357
|
+
|
|
16358
|
+
// apps/api/src/preview-session.ts
|
|
16359
|
+
var sessionSchema2 = external_exports.object({
|
|
16360
|
+
organization: external_exports.string(),
|
|
16361
|
+
project: external_exports.string(),
|
|
16362
|
+
actor: external_exports.string(),
|
|
16363
|
+
hostname: external_exports.string(),
|
|
16364
|
+
generation: external_exports.string().uuid(),
|
|
16365
|
+
port: external_exports.number().int().min(1).max(65535),
|
|
16366
|
+
expiresAt: external_exports.number(),
|
|
16367
|
+
tunnel: external_exports.string().uuid().nullable()
|
|
16368
|
+
});
|
|
16369
|
+
var leaseMs = 9e4;
|
|
16370
|
+
var PreviewSession = class extends DurableObject {
|
|
16371
|
+
async read() {
|
|
16372
|
+
const value = await this.ctx.storage.get("session");
|
|
16373
|
+
return value ? sessionSchema2.parse(value) : null;
|
|
16374
|
+
}
|
|
16375
|
+
async resolve() {
|
|
16376
|
+
const session = await this.read();
|
|
16377
|
+
return session && session.expiresAt > Date.now() && session.tunnel ? {
|
|
16378
|
+
organization: session.organization,
|
|
16379
|
+
project: session.project,
|
|
16380
|
+
hostname: session.hostname
|
|
16381
|
+
} : null;
|
|
16382
|
+
}
|
|
16383
|
+
async start(input) {
|
|
16384
|
+
return this.ctx.blockConcurrencyWhile(async () => {
|
|
16385
|
+
const old = await this.read();
|
|
16386
|
+
if (old && old.expiresAt > Date.now() && old.generation !== input.generation)
|
|
16387
|
+
return {
|
|
16388
|
+
error: "This worktree already has a running preview. Stop it or wait 90 seconds for its lease to expire."
|
|
16389
|
+
};
|
|
16390
|
+
const provider = new PreviewTunnel(this.env);
|
|
16391
|
+
const next = {
|
|
16392
|
+
...input,
|
|
16393
|
+
expiresAt: 0,
|
|
16394
|
+
tunnel: old?.tunnel ?? null
|
|
16395
|
+
};
|
|
16396
|
+
await this.ctx.storage.put("session", next);
|
|
16397
|
+
await this.ctx.storage.setAlarm(Date.now() + leaseMs);
|
|
16398
|
+
const tunnel = await provider.ensure(
|
|
16399
|
+
`domino-${input.hostname.split(".")[0]}`,
|
|
16400
|
+
input.hostname,
|
|
16401
|
+
input.port
|
|
16402
|
+
);
|
|
16403
|
+
if (!old || old.generation !== input.generation || old.expiresAt <= Date.now())
|
|
16404
|
+
await provider.revoke(tunnel);
|
|
16405
|
+
const token = await provider.token(tunnel);
|
|
16406
|
+
await this.ctx.storage.put("session", {
|
|
16407
|
+
...next,
|
|
16408
|
+
tunnel,
|
|
16409
|
+
expiresAt: Date.now() + leaseMs
|
|
16410
|
+
});
|
|
16411
|
+
await this.ctx.storage.delete("retireAt");
|
|
16412
|
+
await this.ctx.storage.setAlarm(Date.now() + leaseMs);
|
|
16413
|
+
return { url: `https://${input.hostname}`, token, leaseMs };
|
|
16414
|
+
});
|
|
16415
|
+
}
|
|
16416
|
+
async heartbeat(generation) {
|
|
16417
|
+
return this.ctx.blockConcurrencyWhile(async () => {
|
|
16418
|
+
const session = await this.read();
|
|
16419
|
+
if (!session || session.generation !== generation || session.expiresAt <= Date.now())
|
|
16420
|
+
return false;
|
|
16421
|
+
session.expiresAt = Date.now() + leaseMs;
|
|
16422
|
+
await this.ctx.storage.put("session", session);
|
|
16423
|
+
await this.ctx.storage.setAlarm(session.expiresAt);
|
|
16424
|
+
return true;
|
|
16425
|
+
});
|
|
16426
|
+
}
|
|
16427
|
+
async stop(generation) {
|
|
16428
|
+
return this.ctx.blockConcurrencyWhile(async () => {
|
|
16429
|
+
const session = await this.read();
|
|
16430
|
+
if (!session || session.generation !== generation) return;
|
|
16431
|
+
await this.ctx.storage.put("session", { ...session, expiresAt: 0 });
|
|
16432
|
+
await this.ctx.storage.setAlarm(Date.now() + 1e3);
|
|
16433
|
+
});
|
|
16434
|
+
}
|
|
16435
|
+
async alarm() {
|
|
16436
|
+
await this.ctx.blockConcurrencyWhile(async () => {
|
|
16437
|
+
const session = await this.read();
|
|
16438
|
+
if (!session || session.expiresAt > Date.now()) return;
|
|
16439
|
+
const provider = new PreviewTunnel(this.env);
|
|
16440
|
+
const tunnel = session.tunnel ?? await provider.lookup(`domino-${session.hostname.split(".")[0]}`);
|
|
16441
|
+
if (!tunnel) return;
|
|
16442
|
+
await this.ctx.storage.put("session", { ...session, tunnel });
|
|
16443
|
+
await provider.revoke(tunnel);
|
|
16444
|
+
const retireAt = await this.ctx.storage.get("retireAt") ?? Date.now() + 7 * 864e5;
|
|
16445
|
+
if (retireAt <= Date.now()) {
|
|
16446
|
+
await provider.remove(tunnel, session.hostname);
|
|
16447
|
+
await this.ctx.storage.put("session", {
|
|
16448
|
+
...session,
|
|
16449
|
+
tunnel: null,
|
|
16450
|
+
expiresAt: 0
|
|
16451
|
+
});
|
|
16452
|
+
await this.ctx.storage.delete("retireAt");
|
|
16453
|
+
} else {
|
|
16454
|
+
await this.ctx.storage.put("retireAt", retireAt);
|
|
16455
|
+
await this.ctx.storage.setAlarm(retireAt);
|
|
16456
|
+
}
|
|
16457
|
+
});
|
|
16458
|
+
}
|
|
16459
|
+
};
|
|
16460
|
+
|
|
15922
16461
|
// apps/api/src/index.ts
|
|
15923
16462
|
import { WorkerEntrypoint as WorkerEntrypoint3 } from "cloudflare:workers";
|
|
15924
16463
|
|
|
@@ -15932,6 +16471,7 @@ init_zod();
|
|
|
15932
16471
|
init_zod();
|
|
15933
16472
|
init_dist();
|
|
15934
16473
|
init_dist();
|
|
16474
|
+
init_zod();
|
|
15935
16475
|
var repositorySchema = external_exports.object({
|
|
15936
16476
|
id: external_exports.string().uuid(),
|
|
15937
16477
|
organization: identifier,
|
|
@@ -15960,6 +16500,7 @@ var stageRequestSchema = external_exports.object({
|
|
|
15960
16500
|
requestId: external_exports.string().uuid()
|
|
15961
16501
|
}).strict();
|
|
15962
16502
|
var buildIdentitySchema = external_exports.object({
|
|
16503
|
+
runtime: external_exports.enum(["simulation", "hosted-test"]).default("simulation"),
|
|
15963
16504
|
id: external_exports.string().uuid(),
|
|
15964
16505
|
repository: external_exports.string().uuid(),
|
|
15965
16506
|
organization: identifier,
|
|
@@ -16011,6 +16552,30 @@ var buildArtifactsSchema = external_exports.object({
|
|
|
16011
16552
|
if (value.assets.reduce((sum, a) => sum + a.size, 0) > 1e7)
|
|
16012
16553
|
ctx.addIssue({ code: "custom", message: "Staging assets exceed 10 MB" });
|
|
16013
16554
|
});
|
|
16555
|
+
var developmentPreflightSchema = external_exports.object({
|
|
16556
|
+
organization: external_exports.string(),
|
|
16557
|
+
project: external_exports.string(),
|
|
16558
|
+
environment: external_exports.enum(["test", "live"]),
|
|
16559
|
+
identity: external_exports.object({
|
|
16560
|
+
exchange: external_exports.literal("supported"),
|
|
16561
|
+
activeIntegrations: external_exports.number().int().nonnegative()
|
|
16562
|
+
}).optional(),
|
|
16563
|
+
staging: external_exports.enum(["configured", "blocked"]),
|
|
16564
|
+
development: external_exports.object({
|
|
16565
|
+
status: external_exports.enum(["configured", "blocked"]),
|
|
16566
|
+
missing: external_exports.array(external_exports.string()),
|
|
16567
|
+
authentication: external_exports.array(external_exports.string())
|
|
16568
|
+
}),
|
|
16569
|
+
checks: external_exports.array(
|
|
16570
|
+
external_exports.object({
|
|
16571
|
+
id: external_exports.string(),
|
|
16572
|
+
status: external_exports.enum(["ok", "error"]),
|
|
16573
|
+
message: external_exports.string(),
|
|
16574
|
+
missing: external_exports.array(external_exports.string()).optional(),
|
|
16575
|
+
issues: external_exports.array(external_exports.string()).optional()
|
|
16576
|
+
})
|
|
16577
|
+
)
|
|
16578
|
+
});
|
|
16014
16579
|
var environmentSchema = external_exports.enum(["test", "live"]);
|
|
16015
16580
|
var roleSchema = external_exports.enum(["editor", "reviewer", "staff"]);
|
|
16016
16581
|
var permissionSchema = external_exports.enum([
|
|
@@ -16027,7 +16592,12 @@ var rolePermissions = {
|
|
|
16027
16592
|
reviewer: ["read", "review"],
|
|
16028
16593
|
staff: ["read", "confirm", "handover"]
|
|
16029
16594
|
};
|
|
16595
|
+
var projectSetupSchema = external_exports.object({
|
|
16596
|
+
mode: external_exports.enum(["new", "existing"]),
|
|
16597
|
+
brief: external_exports.string().trim().min(1).max(4e3)
|
|
16598
|
+
}).strict();
|
|
16030
16599
|
var projectSchema = external_exports.object({
|
|
16600
|
+
setup: projectSetupSchema.optional(),
|
|
16031
16601
|
id: external_exports.string().min(1),
|
|
16032
16602
|
organization: identifier,
|
|
16033
16603
|
project: identifier,
|
|
@@ -16038,7 +16608,11 @@ var projectSchema = external_exports.object({
|
|
|
16038
16608
|
permissions: external_exports.array(permissionSchema),
|
|
16039
16609
|
environments: external_exports.array(environmentSchema)
|
|
16040
16610
|
});
|
|
16041
|
-
var projectInputSchema = external_exports.object({
|
|
16611
|
+
var projectInputSchema = external_exports.object({
|
|
16612
|
+
project: identifier,
|
|
16613
|
+
name: external_exports.string().trim().min(1).max(80),
|
|
16614
|
+
setup: projectSetupSchema.optional()
|
|
16615
|
+
}).strict();
|
|
16042
16616
|
var grantSchema = external_exports.object({
|
|
16043
16617
|
subject: external_exports.string().min(1).max(200),
|
|
16044
16618
|
roles: external_exports.array(roleSchema),
|
|
@@ -16145,87 +16719,41 @@ var memberDetailSchema = external_exports.object({
|
|
|
16145
16719
|
member: memberSchema,
|
|
16146
16720
|
progress: memberProgressSchema,
|
|
16147
16721
|
passes: external_exports.array(
|
|
16148
|
-
external_exports.object({
|
|
16149
|
-
provider: external_exports.string(),
|
|
16150
|
-
event: external_exports.string(),
|
|
16151
|
-
subject: external_exports.string(),
|
|
16152
|
-
attempt: identifier.nullable()
|
|
16153
|
-
})
|
|
16154
|
-
).default([]),
|
|
16155
|
-
wallet: external_exports.object({
|
|
16156
|
-
connection: walletConnectionSchema.nullable(),
|
|
16157
|
-
observation: external_exports.object({ attempt: identifier, balance: solanaBalanceSchema }).nullable()
|
|
16158
|
-
}).default({ connection: null, observation: null })
|
|
16159
|
-
});
|
|
16160
|
-
var stockInputSchema = external_exports.object({
|
|
16161
|
-
item: identifier,
|
|
16162
|
-
quantity: external_exports.number().int().positive().max(1e6),
|
|
16163
|
-
actionId: identifier
|
|
16164
|
-
}).strict();
|
|
16165
|
-
var tokenInputSchema = external_exports.object({
|
|
16166
|
-
name: external_exports.string().trim().min(1).max(80),
|
|
16167
|
-
project: identifier,
|
|
16168
|
-
environment: environmentSchema,
|
|
16169
|
-
permissions: external_exports.array(permissionSchema).min(1),
|
|
16170
|
-
days: external_exports.number().int().min(1).max(90).default(30)
|
|
16171
|
-
}).strict();
|
|
16172
|
-
var tokenSchema = external_exports.object({
|
|
16173
|
-
id: external_exports.string(),
|
|
16174
|
-
name: external_exports.string(),
|
|
16175
|
-
project: external_exports.string(),
|
|
16176
|
-
environment: environmentSchema,
|
|
16177
|
-
permissions: external_exports.array(permissionSchema),
|
|
16178
|
-
expiresAt: external_exports.number()
|
|
16179
|
-
});
|
|
16180
|
-
|
|
16181
|
-
// apps/api/src/staging.ts
|
|
16182
|
-
init_dist();
|
|
16183
|
-
|
|
16184
|
-
// packages/sdk/dist/portal-proxy.js
|
|
16185
|
-
async function proxyParticipant(request, upstream) {
|
|
16186
|
-
const incoming = new URL(request.url);
|
|
16187
|
-
const path = incoming.pathname.slice("/relay".length);
|
|
16188
|
-
const allowed = incoming.pathname.startsWith("/relay/") && (/^\/v1\/auth\/(providers|connections|start|authorize|complete|session|logout|email\/start|email\/verify|callback\/(google|apple|x|discord))$/.test(
|
|
16189
|
-
path
|
|
16190
|
-
) || /^\/v1\/me\/(progress|experience|attempts|evidence|pass|collection-pass|wallet(?:\/challenge|\/verify)?)$/.test(
|
|
16191
|
-
path
|
|
16192
|
-
) || /^\/v1\/attempts\/[\w-]+(?:\/retry)?$/.test(path));
|
|
16193
|
-
if (!allowed || !["GET", "POST"].includes(request.method))
|
|
16194
|
-
return Response.json({ error: "Route not found" }, { status: 404 });
|
|
16195
|
-
const target = new URL(upstream.baseUrl);
|
|
16196
|
-
if (target.protocol !== "https:" || target.username || target.password || target.pathname !== "/" || target.search || target.hash)
|
|
16197
|
-
return Response.json(
|
|
16198
|
-
{ error: "Participant API is not configured" },
|
|
16199
|
-
{ status: 503 }
|
|
16200
|
-
);
|
|
16201
|
-
target.pathname = path;
|
|
16202
|
-
target.search = incoming.search;
|
|
16203
|
-
const headers = new Headers();
|
|
16204
|
-
for (const name of [
|
|
16205
|
-
"Cookie",
|
|
16206
|
-
"Origin",
|
|
16207
|
-
"Content-Type",
|
|
16208
|
-
"X-Relay-CSRF",
|
|
16209
|
-
"X-Relay-Session",
|
|
16210
|
-
"X-Relay-Upload-ID",
|
|
16211
|
-
"CF-Connecting-IP"
|
|
16212
|
-
]) {
|
|
16213
|
-
const value = request.headers.get(name);
|
|
16214
|
-
if (value) headers.set(name, value);
|
|
16215
|
-
}
|
|
16216
|
-
const response = await upstream.fetch(target, {
|
|
16217
|
-
method: request.method,
|
|
16218
|
-
headers,
|
|
16219
|
-
body: request.method === "GET" ? void 0 : await request.arrayBuffer(),
|
|
16220
|
-
redirect: "manual"
|
|
16221
|
-
});
|
|
16222
|
-
const resultHeaders = new Headers(response.headers);
|
|
16223
|
-
resultHeaders.set("Cache-Control", "no-store");
|
|
16224
|
-
return new Response(response.body, {
|
|
16225
|
-
status: response.status,
|
|
16226
|
-
headers: resultHeaders
|
|
16227
|
-
});
|
|
16228
|
-
}
|
|
16722
|
+
external_exports.object({
|
|
16723
|
+
provider: external_exports.string(),
|
|
16724
|
+
event: external_exports.string(),
|
|
16725
|
+
subject: external_exports.string(),
|
|
16726
|
+
attempt: identifier.nullable()
|
|
16727
|
+
})
|
|
16728
|
+
).default([]),
|
|
16729
|
+
wallet: external_exports.object({
|
|
16730
|
+
connection: walletConnectionSchema.nullable(),
|
|
16731
|
+
observation: external_exports.object({ attempt: identifier, balance: solanaBalanceSchema }).nullable()
|
|
16732
|
+
}).default({ connection: null, observation: null })
|
|
16733
|
+
});
|
|
16734
|
+
var stockInputSchema = external_exports.object({
|
|
16735
|
+
item: identifier,
|
|
16736
|
+
quantity: external_exports.number().int().positive().max(1e6),
|
|
16737
|
+
actionId: identifier
|
|
16738
|
+
}).strict();
|
|
16739
|
+
var tokenInputSchema = external_exports.object({
|
|
16740
|
+
name: external_exports.string().trim().min(1).max(80),
|
|
16741
|
+
project: identifier,
|
|
16742
|
+
environment: environmentSchema,
|
|
16743
|
+
permissions: external_exports.array(permissionSchema).min(1),
|
|
16744
|
+
days: external_exports.number().int().min(1).max(90).default(30)
|
|
16745
|
+
}).strict();
|
|
16746
|
+
var tokenSchema = external_exports.object({
|
|
16747
|
+
id: external_exports.string(),
|
|
16748
|
+
name: external_exports.string(),
|
|
16749
|
+
project: external_exports.string(),
|
|
16750
|
+
environment: environmentSchema,
|
|
16751
|
+
permissions: external_exports.array(permissionSchema),
|
|
16752
|
+
expiresAt: external_exports.number()
|
|
16753
|
+
});
|
|
16754
|
+
|
|
16755
|
+
// apps/api/src/staging.ts
|
|
16756
|
+
init_dist();
|
|
16229
16757
|
|
|
16230
16758
|
// apps/api/src/quest-catalog.ts
|
|
16231
16759
|
init_dist();
|
|
@@ -17048,7 +17576,7 @@ var ManagementReads = class {
|
|
|
17048
17576
|
// apps/api/src/ledger.ts
|
|
17049
17577
|
init_zod();
|
|
17050
17578
|
init_dist();
|
|
17051
|
-
import { DurableObject } from "cloudflare:workers";
|
|
17579
|
+
import { DurableObject as DurableObject2 } from "cloudflare:workers";
|
|
17052
17580
|
|
|
17053
17581
|
// apps/api/src/quest-graph.ts
|
|
17054
17582
|
init_dist();
|
|
@@ -17222,36 +17750,6 @@ function pageQuery(params) {
|
|
|
17222
17750
|
return entries;
|
|
17223
17751
|
}
|
|
17224
17752
|
|
|
17225
|
-
// apps/api/src/auth-utils.ts
|
|
17226
|
-
function cookie(request, name) {
|
|
17227
|
-
return request.headers.get("Cookie")?.split(";").map((value) => value.trim()).find((value) => value.startsWith(name + "="))?.slice(name.length + 1) ?? "";
|
|
17228
|
-
}
|
|
17229
|
-
function bindingCookie(value, secure, name = "relay_auth_flow") {
|
|
17230
|
-
return `${name}=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${value ? 600 : 0}${secure ? "; Secure" : ""}`;
|
|
17231
|
-
}
|
|
17232
|
-
async function hash2(value) {
|
|
17233
|
-
const bytes = await crypto.subtle.digest(
|
|
17234
|
-
"SHA-256",
|
|
17235
|
-
new TextEncoder().encode(value)
|
|
17236
|
-
);
|
|
17237
|
-
return Array.from(
|
|
17238
|
-
new Uint8Array(bytes),
|
|
17239
|
-
(b) => b.toString(16).padStart(2, "0")
|
|
17240
|
-
).join("");
|
|
17241
|
-
}
|
|
17242
|
-
function sessionToken(request) {
|
|
17243
|
-
const bearer = request.headers.get("Authorization");
|
|
17244
|
-
if (bearer) return bearer.startsWith("Bearer ") ? bearer.slice(7) : "";
|
|
17245
|
-
return request.headers.get("Cookie")?.split(";").map((x) => x.trim()).find((x) => x.startsWith("relay_session="))?.slice("relay_session=".length) ?? "";
|
|
17246
|
-
}
|
|
17247
|
-
function sessionCookie(token, secure) {
|
|
17248
|
-
return `relay_session=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${token ? 28800 : 0}${secure ? "; Secure" : ""}`;
|
|
17249
|
-
}
|
|
17250
|
-
function browserMutationAllowed(request, env) {
|
|
17251
|
-
const origin = request.headers.get("Origin");
|
|
17252
|
-
return request.headers.get("X-Relay-CSRF") === "1" && !!origin && (env.AUTH_ORIGINS ?? "").split(",").map((value) => value.trim()).includes(origin);
|
|
17253
|
-
}
|
|
17254
|
-
|
|
17255
17753
|
// node_modules/.pnpm/base-x@5.0.1/node_modules/base-x/src/esm/index.js
|
|
17256
17754
|
function base2(ALPHABET2) {
|
|
17257
17755
|
if (ALPHABET2.length >= 255) {
|
|
@@ -17507,7 +18005,7 @@ function json2(value) {
|
|
|
17507
18005
|
function parse3(schema, data) {
|
|
17508
18006
|
return schema.parse(JSON.parse(data));
|
|
17509
18007
|
}
|
|
17510
|
-
var OrganizationLedger = class extends
|
|
18008
|
+
var OrganizationLedger = class extends DurableObject2 {
|
|
17511
18009
|
sql;
|
|
17512
18010
|
management;
|
|
17513
18011
|
catalog;
|
|
@@ -19411,6 +19909,9 @@ var OrganizationLedger = class extends DurableObject {
|
|
|
19411
19909
|
}
|
|
19412
19910
|
};
|
|
19413
19911
|
|
|
19912
|
+
// apps/api/src/auth.ts
|
|
19913
|
+
init_dist();
|
|
19914
|
+
|
|
19414
19915
|
// apps/api/src/account-profile.ts
|
|
19415
19916
|
init_zod();
|
|
19416
19917
|
|
|
@@ -20541,11 +21042,14 @@ async function profileFromStoredGrant(env, sealed, subject) {
|
|
|
20541
21042
|
|
|
20542
21043
|
// apps/api/src/auth.ts
|
|
20543
21044
|
init_dist();
|
|
20544
|
-
import { DurableObject as
|
|
20545
|
-
var AuthStore = class extends
|
|
21045
|
+
import { DurableObject as DurableObject3 } from "cloudflare:workers";
|
|
21046
|
+
var AuthStore = class extends DurableObject3 {
|
|
20546
21047
|
constructor(ctx, env) {
|
|
20547
21048
|
super(ctx, env);
|
|
20548
21049
|
ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS identities (scope TEXT NOT NULL, issuer TEXT NOT NULL, subject TEXT NOT NULL, member TEXT NOT NULL, PRIMARY KEY(scope,issuer,subject));
|
|
21050
|
+
CREATE TABLE IF NOT EXISTS identity_integrations (id TEXT PRIMARY KEY, secret TEXT NOT NULL, data TEXT NOT NULL);
|
|
21051
|
+
CREATE TABLE IF NOT EXISTS integration_sessions (hash TEXT PRIMARY KEY, integration TEXT NOT NULL, subject TEXT NOT NULL);
|
|
21052
|
+
CREATE TABLE IF NOT EXISTS integration_audit (actor TEXT NOT NULL, action TEXT NOT NULL, integration TEXT NOT NULL, at INTEGER NOT NULL);
|
|
20549
21053
|
CREATE TABLE IF NOT EXISTS sessions (hash TEXT PRIMARY KEY, expires INTEGER NOT NULL, data TEXT NOT NULL);
|
|
20550
21054
|
CREATE TABLE IF NOT EXISTS auth_flows (id TEXT PRIMARY KEY, expires INTEGER NOT NULL, data TEXT NOT NULL);
|
|
20551
21055
|
CREATE TABLE IF NOT EXISTS connections (scope TEXT NOT NULL, issuer TEXT NOT NULL, subject TEXT NOT NULL, member TEXT NOT NULL, login INTEGER NOT NULL, provider TEXT NOT NULL, sealed TEXT NOT NULL, PRIMARY KEY(scope,issuer,subject));
|
|
@@ -20554,6 +21058,134 @@ var AuthStore = class extends DurableObject2 {
|
|
|
20554
21058
|
CREATE TABLE IF NOT EXISTS wallet_challenges (id TEXT PRIMARY KEY, expires INTEGER NOT NULL, data TEXT NOT NULL, result TEXT);
|
|
20555
21059
|
CREATE TABLE IF NOT EXISTS wallet_connections (scope TEXT NOT NULL, project TEXT NOT NULL, member TEXT NOT NULL, data TEXT NOT NULL, PRIMARY KEY(scope,project,member));`);
|
|
20556
21060
|
}
|
|
21061
|
+
integrations(scope) {
|
|
21062
|
+
return this.ctx.storage.sql.exec("SELECT data FROM identity_integrations").toArray().map((row) => identityIntegrationSchema.parse(JSON.parse(row.data))).filter(
|
|
21063
|
+
(item) => item.organization === scope.organization && item.project === scope.project && item.environment === scope.environment
|
|
21064
|
+
);
|
|
21065
|
+
}
|
|
21066
|
+
async createIntegration(scope, input, actor) {
|
|
21067
|
+
const record2 = identityIntegrationSchema.parse({
|
|
21068
|
+
...authScopeSchema.parse(scope),
|
|
21069
|
+
...identityIntegrationInputSchema.parse(input),
|
|
21070
|
+
id: crypto.randomUUID(),
|
|
21071
|
+
expiresAt: Date.now() + 90 * 864e5
|
|
21072
|
+
});
|
|
21073
|
+
const token = "di_" + crypto.randomUUID() + crypto.randomUUID();
|
|
21074
|
+
const digest = await hash2(token);
|
|
21075
|
+
this.ctx.storage.transactionSync(() => {
|
|
21076
|
+
this.ctx.storage.sql.exec(
|
|
21077
|
+
"INSERT INTO identity_integrations VALUES(?,?,?)",
|
|
21078
|
+
record2.id,
|
|
21079
|
+
digest,
|
|
21080
|
+
JSON.stringify(record2)
|
|
21081
|
+
);
|
|
21082
|
+
this.ctx.storage.sql.exec(
|
|
21083
|
+
"INSERT INTO integration_audit VALUES(?,?,?,?)",
|
|
21084
|
+
actor,
|
|
21085
|
+
"created",
|
|
21086
|
+
record2.id,
|
|
21087
|
+
Date.now()
|
|
21088
|
+
);
|
|
21089
|
+
});
|
|
21090
|
+
return { ...record2, token };
|
|
21091
|
+
}
|
|
21092
|
+
async changeIntegration(scope, id2, actor, rotate) {
|
|
21093
|
+
const token = "di_" + crypto.randomUUID() + crypto.randomUUID();
|
|
21094
|
+
const digest = await hash2(token);
|
|
21095
|
+
return this.ctx.storage.transactionSync(() => {
|
|
21096
|
+
const record2 = this.integrations(scope).find((item) => item.id === id2);
|
|
21097
|
+
if (!record2) return null;
|
|
21098
|
+
this.ctx.storage.sql.exec(
|
|
21099
|
+
"INSERT INTO integration_audit VALUES(?,?,?,?)",
|
|
21100
|
+
actor,
|
|
21101
|
+
rotate ? "rotated" : "revoked",
|
|
21102
|
+
id2,
|
|
21103
|
+
Date.now()
|
|
21104
|
+
);
|
|
21105
|
+
if (rotate) {
|
|
21106
|
+
const next = { ...record2, expiresAt: Date.now() + 90 * 864e5 };
|
|
21107
|
+
this.ctx.storage.sql.exec(
|
|
21108
|
+
"UPDATE identity_integrations SET secret=?,data=? WHERE id=?",
|
|
21109
|
+
digest,
|
|
21110
|
+
JSON.stringify(next),
|
|
21111
|
+
id2
|
|
21112
|
+
);
|
|
21113
|
+
return { ...next, token };
|
|
21114
|
+
}
|
|
21115
|
+
this.ctx.storage.sql.exec(
|
|
21116
|
+
"DELETE FROM sessions WHERE hash IN (SELECT hash FROM integration_sessions WHERE integration=?)",
|
|
21117
|
+
id2
|
|
21118
|
+
);
|
|
21119
|
+
this.ctx.storage.sql.exec(
|
|
21120
|
+
"DELETE FROM integration_sessions WHERE integration=?",
|
|
21121
|
+
id2
|
|
21122
|
+
);
|
|
21123
|
+
this.ctx.storage.sql.exec(
|
|
21124
|
+
"DELETE FROM identity_integrations WHERE id=?",
|
|
21125
|
+
id2
|
|
21126
|
+
);
|
|
21127
|
+
return null;
|
|
21128
|
+
});
|
|
21129
|
+
}
|
|
21130
|
+
async verifyIntegration(token) {
|
|
21131
|
+
const row = this.ctx.storage.sql.exec(
|
|
21132
|
+
"SELECT data FROM identity_integrations WHERE secret=?",
|
|
21133
|
+
await hash2(token)
|
|
21134
|
+
).toArray()[0];
|
|
21135
|
+
const record2 = row ? identityIntegrationSchema.parse(JSON.parse(row.data)) : null;
|
|
21136
|
+
return record2 && record2.expiresAt > Date.now() ? record2 : null;
|
|
21137
|
+
}
|
|
21138
|
+
async integrationSession(token, id2, subject) {
|
|
21139
|
+
const digest = await hash2(token);
|
|
21140
|
+
const row = this.ctx.storage.sql.exec(
|
|
21141
|
+
"SELECT integration,subject FROM integration_sessions WHERE hash=?",
|
|
21142
|
+
digest
|
|
21143
|
+
).toArray()[0];
|
|
21144
|
+
if (!row || row.integration !== id2 || subject !== void 0 && row.subject !== subject)
|
|
21145
|
+
return null;
|
|
21146
|
+
return this.resolve(token, id2);
|
|
21147
|
+
}
|
|
21148
|
+
async exchangeParticipant(secret, value) {
|
|
21149
|
+
const input = participantExchangeSchema.parse(value);
|
|
21150
|
+
const record2 = await this.verifyIntegration(secret);
|
|
21151
|
+
if (!record2) return { kind: "denied" };
|
|
21152
|
+
if (input.previousToken) {
|
|
21153
|
+
const session = await this.integrationSession(
|
|
21154
|
+
input.previousToken,
|
|
21155
|
+
record2.id,
|
|
21156
|
+
input.subject
|
|
21157
|
+
);
|
|
21158
|
+
if (session)
|
|
21159
|
+
return {
|
|
21160
|
+
kind: "issued",
|
|
21161
|
+
result: { token: input.previousToken, session }
|
|
21162
|
+
};
|
|
21163
|
+
}
|
|
21164
|
+
if (!this.rateLimit("integration:" + record2.id, 600, 6e4))
|
|
21165
|
+
return { kind: "limited" };
|
|
21166
|
+
const result = await this.issue({
|
|
21167
|
+
organization: record2.organization,
|
|
21168
|
+
project: record2.project,
|
|
21169
|
+
environment: record2.environment,
|
|
21170
|
+
identity: { issuer: "app_" + record2.id, subject: input.subject },
|
|
21171
|
+
role: "member"
|
|
21172
|
+
});
|
|
21173
|
+
if (!await this.verifyIntegration(secret)) {
|
|
21174
|
+
await this.revoke(result.token);
|
|
21175
|
+
return { kind: "denied" };
|
|
21176
|
+
}
|
|
21177
|
+
const digest = await hash2(result.token);
|
|
21178
|
+
this.ctx.storage.sql.exec(
|
|
21179
|
+
"DELETE FROM integration_sessions WHERE hash NOT IN (SELECT hash FROM sessions)"
|
|
21180
|
+
);
|
|
21181
|
+
this.ctx.storage.sql.exec(
|
|
21182
|
+
"INSERT INTO integration_sessions VALUES(?,?,?)",
|
|
21183
|
+
digest,
|
|
21184
|
+
record2.id,
|
|
21185
|
+
input.subject
|
|
21186
|
+
);
|
|
21187
|
+
return { kind: "issued", result };
|
|
21188
|
+
}
|
|
20557
21189
|
walletConnection(session) {
|
|
20558
21190
|
if (session.principal.kind !== "member") return null;
|
|
20559
21191
|
const row = this.ctx.storage.sql.exec(
|
|
@@ -20917,13 +21549,21 @@ var AuthStore = class extends DurableObject2 {
|
|
|
20917
21549
|
signInEnabled: !!row.login
|
|
20918
21550
|
}));
|
|
20919
21551
|
}
|
|
20920
|
-
async resolve(token) {
|
|
21552
|
+
async resolve(token, integrationId) {
|
|
21553
|
+
const digest = await hash2(token);
|
|
20921
21554
|
const row = this.ctx.storage.sql.exec(
|
|
20922
21555
|
"SELECT data FROM sessions WHERE hash=? AND expires>?",
|
|
20923
|
-
|
|
21556
|
+
digest,
|
|
20924
21557
|
Date.now()
|
|
20925
21558
|
).toArray()[0];
|
|
20926
|
-
|
|
21559
|
+
if (!row) return null;
|
|
21560
|
+
const binding = this.ctx.storage.sql.exec(
|
|
21561
|
+
"SELECT i.data,s.integration FROM integration_sessions s LEFT JOIN identity_integrations i ON i.id=s.integration WHERE s.hash=?",
|
|
21562
|
+
digest
|
|
21563
|
+
).toArray()[0];
|
|
21564
|
+
if (binding && (binding.integration !== integrationId || !binding.data || identityIntegrationSchema.parse(JSON.parse(binding.data)).expiresAt <= Date.now()))
|
|
21565
|
+
return null;
|
|
21566
|
+
return sessionSchema.parse(JSON.parse(row.data));
|
|
20927
21567
|
}
|
|
20928
21568
|
async revoke(token) {
|
|
20929
21569
|
this.ctx.storage.sql.exec(
|
|
@@ -20935,16 +21575,6 @@ var AuthStore = class extends DurableObject2 {
|
|
|
20935
21575
|
|
|
20936
21576
|
// apps/api/src/github-repositories.ts
|
|
20937
21577
|
init_zod();
|
|
20938
|
-
|
|
20939
|
-
// apps/api/src/access-error.ts
|
|
20940
|
-
var AccessError = class extends Error {
|
|
20941
|
-
constructor(message2, status = 403) {
|
|
20942
|
-
super(message2);
|
|
20943
|
-
this.status = status;
|
|
20944
|
-
}
|
|
20945
|
-
};
|
|
20946
|
-
|
|
20947
|
-
// apps/api/src/github-repositories.ts
|
|
20948
21578
|
var githubRepositorySchema = external_exports.object({
|
|
20949
21579
|
kind: external_exports.literal("github"),
|
|
20950
21580
|
owner: external_exports.string().regex(/^[A-Za-z0-9-]+$/),
|
|
@@ -21195,33 +21825,32 @@ var HostingControl = class extends WorkerEntrypoint2 {
|
|
|
21195
21825
|
const artifacts = buildArtifactsSchema.parse(await object2.json());
|
|
21196
21826
|
const releases = batchReleaseSchema.parse(artifacts.quests);
|
|
21197
21827
|
const catalog = catalogDeploymentSchema.omit({ expectedRevision: true }).parse(artifacts.catalog);
|
|
21198
|
-
const ledger = this.env.STAGING_LEDGER.getByName(
|
|
21199
|
-
JSON.stringify([
|
|
21828
|
+
const ledger = (build.runtime === "hosted-test" ? this.env.LEDGER : this.env.STAGING_LEDGER).getByName(
|
|
21829
|
+
JSON.stringify([
|
|
21830
|
+
build.organization,
|
|
21831
|
+
"test",
|
|
21832
|
+
build.runtime === "hosted-test" ? build.project : previewProject(build.id)
|
|
21833
|
+
])
|
|
21200
21834
|
);
|
|
21201
21835
|
const headers = {
|
|
21202
21836
|
"Content-Type": "application/json",
|
|
21203
21837
|
"X-Relay-Actor": "management",
|
|
21204
21838
|
"X-Relay-Actor-ID": build.actor
|
|
21205
21839
|
};
|
|
21206
|
-
|
|
21207
|
-
|
|
21208
|
-
|
|
21209
|
-
|
|
21210
|
-
|
|
21211
|
-
|
|
21212
|
-
})
|
|
21213
|
-
);
|
|
21214
|
-
if (!published.ok) throw new Error(await published.text());
|
|
21215
|
-
}
|
|
21216
|
-
const current = await ledger.fetch(
|
|
21217
|
-
new Request("https://ledger/management/v1/catalog", { headers })
|
|
21840
|
+
const preview = await ledger.fetch(
|
|
21841
|
+
new Request("https://ledger/management/v1/deployments/preview", {
|
|
21842
|
+
method: "POST",
|
|
21843
|
+
headers,
|
|
21844
|
+
body: JSON.stringify({ ...releases, ...catalog })
|
|
21845
|
+
})
|
|
21218
21846
|
);
|
|
21219
|
-
|
|
21847
|
+
if (!preview.ok) throw new Error(await preview.text());
|
|
21848
|
+
const { id: id2 } = external_exports.object({ id: external_exports.string() }).parse(await preview.json());
|
|
21220
21849
|
const deployed = await ledger.fetch(
|
|
21221
|
-
new Request("https://ledger/management/v1/
|
|
21850
|
+
new Request("https://ledger/management/v1/deployments/publish", {
|
|
21222
21851
|
method: "POST",
|
|
21223
21852
|
headers,
|
|
21224
|
-
body: JSON.stringify({
|
|
21853
|
+
body: JSON.stringify({ id: id2 })
|
|
21225
21854
|
})
|
|
21226
21855
|
);
|
|
21227
21856
|
if (!deployed.ok) throw new Error(await deployed.text());
|
|
@@ -21250,6 +21879,15 @@ async function stagingRequest(request, env, participant) {
|
|
|
21250
21879
|
if (!build || !env.STAGING_APPS || !env.STAGING_AUTH || !env.STAGING_LEDGER)
|
|
21251
21880
|
return new Response("No staging build is ready yet", { status: 404 });
|
|
21252
21881
|
if (url2.pathname.startsWith("/relay/")) {
|
|
21882
|
+
if (build.runtime === "hosted-test") {
|
|
21883
|
+
return proxyParticipant(request, {
|
|
21884
|
+
baseUrl: "https://participant.internal",
|
|
21885
|
+
fetch: (input, init) => participant(
|
|
21886
|
+
new Request(input, init),
|
|
21887
|
+
hostedParticipantEnv(env, build, url2.origin)
|
|
21888
|
+
)
|
|
21889
|
+
});
|
|
21890
|
+
}
|
|
21253
21891
|
if (url2.pathname.startsWith("/relay/v1/auth/") && !["/relay/v1/auth/session", "/relay/v1/auth/logout"].includes(
|
|
21254
21892
|
url2.pathname
|
|
21255
21893
|
))
|
|
@@ -21332,7 +21970,7 @@ async function stagingRequest(request, env, participant) {
|
|
|
21332
21970
|
return response.headers.get("Content-Type")?.includes("text/html") ? new HTMLRewriter().on("body", {
|
|
21333
21971
|
element(element) {
|
|
21334
21972
|
element.prepend(
|
|
21335
|
-
'<aside style="position:sticky;top:0;z-index:99999;padding:10px;background:#fff3cd;color:#332701;text-align:center;font:14px system-ui">Staging preview \xB7 simulated participants \xB7 no live data</aside>
|
|
21973
|
+
'<aside style="position:sticky;top:0;z-index:99999;padding:10px;background:#fff3cd;color:#332701;text-align:center;font:14px system-ui">Staging preview \xB7 ' + (build.runtime === "hosted-test" ? "test data \xB7 real sign-in" : "simulated participants \xB7 no live data") + "</aside>",
|
|
21336
21974
|
{ html: true }
|
|
21337
21975
|
);
|
|
21338
21976
|
}
|
|
@@ -22594,6 +23232,27 @@ function localParticipantRequest(request, env) {
|
|
|
22594
23232
|
return env.LOCAL_DEVELOPMENT === "true" && ["localhost", "127.0.0.1", "[::1]"].includes(new URL(request.url).hostname);
|
|
22595
23233
|
}
|
|
22596
23234
|
|
|
23235
|
+
// apps/api/src/management.ts
|
|
23236
|
+
init_dist();
|
|
23237
|
+
|
|
23238
|
+
// apps/api/src/development-preflight.ts
|
|
23239
|
+
function stagingConfigurationIssues(env) {
|
|
23240
|
+
const required2 = {
|
|
23241
|
+
BUILDER: env.BUILDER,
|
|
23242
|
+
HOSTING_ASSETS: env.HOSTING_ASSETS,
|
|
23243
|
+
STAGING_DOMAIN: env.STAGING_DOMAIN,
|
|
23244
|
+
STAGING_APPS: env.STAGING_APPS,
|
|
23245
|
+
STAGING_AUTH: env.STAGING_AUTH,
|
|
23246
|
+
STAGING_LEDGER: env.STAGING_LEDGER,
|
|
23247
|
+
GITHUB_APP_ID: env.GITHUB_APP_ID,
|
|
23248
|
+
GITHUB_APP_PRIVATE_KEY: env.GITHUB_APP_PRIVATE_KEY,
|
|
23249
|
+
GITHUB_INSTALLATION_ID: env.GITHUB_INSTALLATION_ID,
|
|
23250
|
+
GITHUB_REPOSITORY_OWNER: env.GITHUB_REPOSITORY_OWNER,
|
|
23251
|
+
GIT_PUBLIC_URL: env.GIT_PUBLIC_URL
|
|
23252
|
+
};
|
|
23253
|
+
return Object.entries(required2).filter(([, value]) => !value).map(([key]) => key);
|
|
23254
|
+
}
|
|
23255
|
+
|
|
22597
23256
|
// apps/api/src/readiness.ts
|
|
22598
23257
|
function launchReadiness(env, releases, scope) {
|
|
22599
23258
|
const issues = productionParticipantIssues(env);
|
|
@@ -32352,6 +33011,172 @@ async function managementRoutes(request, env) {
|
|
|
32352
33011
|
}
|
|
32353
33012
|
if (!organization || !(await store.projects(organization)).some((p) => p.project === project))
|
|
32354
33013
|
throw new AccessError("Project not found", 404);
|
|
33014
|
+
if (path === "/identity-integrations" || path.startsWith("/identity-integrations/")) {
|
|
33015
|
+
if (personal || cliSession || !admin)
|
|
33016
|
+
throw new AccessError(
|
|
33017
|
+
"An organization administrator must manage identity integrations in Console"
|
|
33018
|
+
);
|
|
33019
|
+
const auth = env.AUTH.getByName("auth-v1");
|
|
33020
|
+
const scope = { organization, project, environment };
|
|
33021
|
+
if (path === "/identity-integrations" && request.method === "GET")
|
|
33022
|
+
return respond(await auth.integrations(scope));
|
|
33023
|
+
if (path === "/identity-integrations" && request.method === "POST")
|
|
33024
|
+
return respond(
|
|
33025
|
+
await auth.createIntegration(
|
|
33026
|
+
scope,
|
|
33027
|
+
identityIntegrationInputSchema.parse(await request.json()),
|
|
33028
|
+
actor
|
|
33029
|
+
),
|
|
33030
|
+
201
|
|
33031
|
+
);
|
|
33032
|
+
const match2 = path.match(
|
|
33033
|
+
/^\/identity-integrations\/([\w-]+)(\/rotate)?$/
|
|
33034
|
+
);
|
|
33035
|
+
if (match2 && (match2[2] && request.method === "POST" || !match2[2] && request.method === "DELETE")) {
|
|
33036
|
+
return respond(
|
|
33037
|
+
await auth.changeIntegration(
|
|
33038
|
+
scope,
|
|
33039
|
+
external_exports.string().uuid().parse(match2[1]),
|
|
33040
|
+
actor,
|
|
33041
|
+
!!match2[2]
|
|
33042
|
+
)
|
|
33043
|
+
);
|
|
33044
|
+
}
|
|
33045
|
+
throw new AccessError("Identity integration route not found", 404);
|
|
33046
|
+
}
|
|
33047
|
+
if (path === "/development/session" && ["POST", "PUT", "DELETE"].includes(request.method)) {
|
|
33048
|
+
requirePermission("publish");
|
|
33049
|
+
if (environment !== "test")
|
|
33050
|
+
throw new AccessError("Development requires the test environment", 422);
|
|
33051
|
+
if (previewConfigurationIssues(env).length || !env.PREVIEW_SESSIONS || !env.PREVIEW_DOMAIN)
|
|
33052
|
+
throw new AccessError(
|
|
33053
|
+
"Shared development previews are not configured. A Domino operator must enable preview hosting. Use --offline only for isolated platform development.",
|
|
33054
|
+
503
|
|
33055
|
+
);
|
|
33056
|
+
const input = external_exports.object({
|
|
33057
|
+
workspace: external_exports.string().uuid(),
|
|
33058
|
+
generation: external_exports.string().uuid(),
|
|
33059
|
+
port: external_exports.number().int().min(1).max(65535).optional()
|
|
33060
|
+
}).strict().parse(await request.json());
|
|
33061
|
+
const id2 = (await hash2(
|
|
33062
|
+
JSON.stringify([organization, project, actor, input.workspace])
|
|
33063
|
+
)).slice(0, 32);
|
|
33064
|
+
const session = env.PREVIEW_SESSIONS.getByName(id2);
|
|
33065
|
+
if (request.method === "DELETE") {
|
|
33066
|
+
await session.stop(input.generation);
|
|
33067
|
+
return respond({ stopped: true });
|
|
33068
|
+
}
|
|
33069
|
+
if (request.method === "PUT") {
|
|
33070
|
+
if (!await session.heartbeat(input.generation))
|
|
33071
|
+
throw new AccessError(
|
|
33072
|
+
"Preview lease expired. Restart domino dev.",
|
|
33073
|
+
409
|
|
33074
|
+
);
|
|
33075
|
+
return respond({ renewed: true });
|
|
33076
|
+
}
|
|
33077
|
+
if (!input.port) throw new AccessError("An app port is required", 400);
|
|
33078
|
+
if (!await env.AUTH.getByName("auth-v1").rateLimit(
|
|
33079
|
+
`preview-start:${organization}:${actor}`,
|
|
33080
|
+
10,
|
|
33081
|
+
6e4
|
|
33082
|
+
))
|
|
33083
|
+
throw new AccessError(
|
|
33084
|
+
"Too many preview starts. Try again shortly.",
|
|
33085
|
+
429
|
|
33086
|
+
);
|
|
33087
|
+
const hostname3 = `${project.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 24)}-${id2}.${env.PREVIEW_DOMAIN}`;
|
|
33088
|
+
const issues = productionParticipantIssues(
|
|
33089
|
+
hostedParticipantEnv(
|
|
33090
|
+
env,
|
|
33091
|
+
{ organization, project },
|
|
33092
|
+
`https://${hostname3}`
|
|
33093
|
+
)
|
|
33094
|
+
);
|
|
33095
|
+
if (issues.length)
|
|
33096
|
+
throw new AccessError(
|
|
33097
|
+
"Hosted test sign-in is not configured. A Domino operator must enable participant authentication.",
|
|
33098
|
+
503
|
|
33099
|
+
);
|
|
33100
|
+
const result = await session.start({
|
|
33101
|
+
organization,
|
|
33102
|
+
project,
|
|
33103
|
+
actor,
|
|
33104
|
+
hostname: hostname3,
|
|
33105
|
+
generation: input.generation,
|
|
33106
|
+
port: input.port
|
|
33107
|
+
});
|
|
33108
|
+
if ("error" in result)
|
|
33109
|
+
throw new AccessError(
|
|
33110
|
+
result.error ?? "Preview is already running",
|
|
33111
|
+
409
|
|
33112
|
+
);
|
|
33113
|
+
return respond(result);
|
|
33114
|
+
}
|
|
33115
|
+
if (path === "/preflight" && request.method === "GET") {
|
|
33116
|
+
requirePermission("read");
|
|
33117
|
+
const missing = stagingConfigurationIssues(env);
|
|
33118
|
+
const repository = await store.repository(organization, project);
|
|
33119
|
+
const authIssues = productionParticipantIssues(
|
|
33120
|
+
hostedParticipantEnv(
|
|
33121
|
+
env,
|
|
33122
|
+
{ organization, project },
|
|
33123
|
+
`https://preflight.${env.PREVIEW_DOMAIN ?? "preview.domino.run"}`
|
|
33124
|
+
)
|
|
33125
|
+
);
|
|
33126
|
+
const checks = [
|
|
33127
|
+
{
|
|
33128
|
+
id: "hosted-test-auth",
|
|
33129
|
+
status: authIssues.length ? "error" : "ok",
|
|
33130
|
+
message: authIssues.length ? "A Domino operator must configure real participant sign-in." : "Real sign-in is configured; complete a sign-in to verify provider availability.",
|
|
33131
|
+
issues: authIssues
|
|
33132
|
+
},
|
|
33133
|
+
{
|
|
33134
|
+
id: "project-access",
|
|
33135
|
+
status: "ok",
|
|
33136
|
+
message: "Project access verified."
|
|
33137
|
+
},
|
|
33138
|
+
{
|
|
33139
|
+
id: "publication",
|
|
33140
|
+
status: permissions.includes("publish") ? "ok" : "error",
|
|
33141
|
+
message: permissions.includes("publish") ? "Publication permission verified." : "Ask a project administrator for publication permission."
|
|
33142
|
+
},
|
|
33143
|
+
{
|
|
33144
|
+
id: "cloud-builds",
|
|
33145
|
+
status: missing.length ? "error" : "ok",
|
|
33146
|
+
message: missing.length ? "A Domino operator must configure cloud hosting before this project can stage." : "Cloud hosting configuration is present. A successful build has not been verified.",
|
|
33147
|
+
missing
|
|
33148
|
+
},
|
|
33149
|
+
{
|
|
33150
|
+
id: "repository",
|
|
33151
|
+
status: repository?.status === "ready" ? "ok" : "error",
|
|
33152
|
+
message: repository?.status === "ready" ? "Hosted repository is ready." : "Create a hosted repository before staging."
|
|
33153
|
+
}
|
|
33154
|
+
];
|
|
33155
|
+
const integrations = await env.AUTH.getByName("auth-v1").integrations({
|
|
33156
|
+
organization,
|
|
33157
|
+
project,
|
|
33158
|
+
environment
|
|
33159
|
+
});
|
|
33160
|
+
const developmentMissing = previewConfigurationIssues(env);
|
|
33161
|
+
return respond({
|
|
33162
|
+
organization,
|
|
33163
|
+
project,
|
|
33164
|
+
environment,
|
|
33165
|
+
identity: {
|
|
33166
|
+
exchange: "supported",
|
|
33167
|
+
activeIntegrations: integrations.filter(
|
|
33168
|
+
(item) => item.expiresAt > Date.now()
|
|
33169
|
+
).length
|
|
33170
|
+
},
|
|
33171
|
+
development: {
|
|
33172
|
+
status: developmentMissing.length || authIssues.length ? "blocked" : "configured",
|
|
33173
|
+
missing: developmentMissing,
|
|
33174
|
+
authentication: authIssues
|
|
33175
|
+
},
|
|
33176
|
+
staging: checks.every((check2) => check2.status === "ok") ? "configured" : "blocked",
|
|
33177
|
+
checks
|
|
33178
|
+
});
|
|
33179
|
+
}
|
|
32355
33180
|
if (path === "/repository" && ["GET", "POST"].includes(request.method)) {
|
|
32356
33181
|
requirePermission(request.method === "POST" ? "publish" : "read");
|
|
32357
33182
|
const publicBase = env.GIT_PUBLIC_URL;
|
|
@@ -32367,15 +33192,45 @@ async function managementRoutes(request, env) {
|
|
|
32367
33192
|
if (repo.status !== "ready") {
|
|
32368
33193
|
if (!env.HOSTING_ASSETS)
|
|
32369
33194
|
throw new AccessError("Starter storage is not configured", 503);
|
|
33195
|
+
const active = await env.HOSTING_ASSETS.get(
|
|
33196
|
+
"starters/campaign-current.json"
|
|
33197
|
+
);
|
|
33198
|
+
const pointer = active ? external_exports.object({
|
|
33199
|
+
format: external_exports.literal(1),
|
|
33200
|
+
key: external_exports.string().regex(/^starters\/campaign\/[a-f0-9]{64}\.json$/),
|
|
33201
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
|
|
33202
|
+
}).parse(await active.json()) : null;
|
|
32370
33203
|
const starter = await env.HOSTING_ASSETS.get(
|
|
32371
|
-
"starters/campaign.json"
|
|
33204
|
+
pointer?.key ?? "starters/campaign.json"
|
|
32372
33205
|
);
|
|
32373
33206
|
if (!starter)
|
|
32374
33207
|
throw new AccessError(
|
|
32375
33208
|
"Campaign starter has not been uploaded",
|
|
32376
33209
|
503
|
|
32377
33210
|
);
|
|
32378
|
-
const
|
|
33211
|
+
const contents = await starter.text();
|
|
33212
|
+
if (pointer && await hash2(contents) !== pointer.sha256)
|
|
33213
|
+
throw new AccessError(
|
|
33214
|
+
"Starter integrity check failed. Contact a Domino operator.",
|
|
33215
|
+
503
|
|
33216
|
+
);
|
|
33217
|
+
const files = starterFilesSchema.parse(JSON.parse(contents));
|
|
33218
|
+
const setup = (await store.projects(organization)).find(
|
|
33219
|
+
(item) => item.project === project
|
|
33220
|
+
)?.setup;
|
|
33221
|
+
if (setup) {
|
|
33222
|
+
const brief = {
|
|
33223
|
+
path: "CAMPAIGN.md",
|
|
33224
|
+
encoding: "utf-8",
|
|
33225
|
+
content: `# Project brief
|
|
33226
|
+
|
|
33227
|
+
${setup.brief}
|
|
33228
|
+
`
|
|
33229
|
+
};
|
|
33230
|
+
const index = files.findIndex((file2) => file2.path === brief.path);
|
|
33231
|
+
if (index < 0) files.push(brief);
|
|
33232
|
+
else files[index] = brief;
|
|
33233
|
+
}
|
|
32379
33234
|
const github = new GitHubRepositories(env);
|
|
32380
33235
|
const provider = await github.provision(repo.id);
|
|
32381
33236
|
await github.seed(provider, files);
|
|
@@ -32403,12 +33258,26 @@ async function managementRoutes(request, env) {
|
|
|
32403
33258
|
"Staging builds require the test environment",
|
|
32404
33259
|
422
|
|
32405
33260
|
);
|
|
32406
|
-
if (
|
|
32407
|
-
throw new AccessError(
|
|
33261
|
+
if (stagingConfigurationIssues(env).length || !env.BUILDER || !env.HOSTING_ASSETS)
|
|
33262
|
+
throw new AccessError(
|
|
33263
|
+
"Cloud builds are not configured. Run domino doctor --remote; a Domino operator must enable cloud hosting.",
|
|
33264
|
+
503
|
|
33265
|
+
);
|
|
32408
33266
|
const repository = await store.repository(organization, project);
|
|
32409
33267
|
if (!repository || repository.status !== "ready")
|
|
32410
33268
|
throw new AccessError("Create a hosted repository first", 409);
|
|
32411
33269
|
if (path === "/builds" && request.method === "POST") {
|
|
33270
|
+
if (productionParticipantIssues(
|
|
33271
|
+
hostedParticipantEnv(
|
|
33272
|
+
env,
|
|
33273
|
+
{ organization, project },
|
|
33274
|
+
`https://stage-${repository.id.replaceAll("-", "")}.${env.STAGING_DOMAIN}`
|
|
33275
|
+
)
|
|
33276
|
+
).length)
|
|
33277
|
+
throw new AccessError(
|
|
33278
|
+
"Hosted test sign-in is not configured. Run domino doctor --remote before staging.",
|
|
33279
|
+
503
|
|
33280
|
+
);
|
|
32412
33281
|
const input = stageRequestSchema.parse(await request.json());
|
|
32413
33282
|
await new GitHubRepositories(env).verifyCommit(
|
|
32414
33283
|
repository.provider,
|
|
@@ -32416,6 +33285,7 @@ async function managementRoutes(request, env) {
|
|
|
32416
33285
|
);
|
|
32417
33286
|
const build2 = await store.reserveBuild({
|
|
32418
33287
|
id: input.requestId,
|
|
33288
|
+
runtime: "hosted-test",
|
|
32419
33289
|
repository: repository.id,
|
|
32420
33290
|
organization,
|
|
32421
33291
|
project,
|
|
@@ -32877,7 +33747,7 @@ async function deviceRoutes(request, env) {
|
|
|
32877
33747
|
|
|
32878
33748
|
// apps/api/src/management-store.ts
|
|
32879
33749
|
init_zod();
|
|
32880
|
-
import { DurableObject as
|
|
33750
|
+
import { DurableObject as DurableObject4 } from "cloudflare:workers";
|
|
32881
33751
|
var repositoryBase = external_exports.object({
|
|
32882
33752
|
id: external_exports.string().uuid(),
|
|
32883
33753
|
organization: external_exports.string(),
|
|
@@ -32893,11 +33763,12 @@ var repositoryRecord = external_exports.discriminatedUnion("status", [
|
|
|
32893
33763
|
provider: githubRepositorySchema
|
|
32894
33764
|
})
|
|
32895
33765
|
]);
|
|
32896
|
-
var ManagementStore = class extends
|
|
33766
|
+
var ManagementStore = class extends DurableObject4 {
|
|
32897
33767
|
constructor(ctx, env) {
|
|
32898
33768
|
super(ctx, env);
|
|
32899
33769
|
ctx.storage.sql.exec(`
|
|
32900
33770
|
CREATE TABLE IF NOT EXISTS projects (organization TEXT, project TEXT, name TEXT NOT NULL, PRIMARY KEY(organization,project));
|
|
33771
|
+
CREATE TABLE IF NOT EXISTS project_setup (organization TEXT, project TEXT, data TEXT NOT NULL, PRIMARY KEY(organization,project));
|
|
32901
33772
|
CREATE TABLE IF NOT EXISTS grants (organization TEXT, project TEXT, subject TEXT, data TEXT NOT NULL, PRIMARY KEY(organization,project,subject));
|
|
32902
33773
|
DROP TABLE IF EXISTS tokens;
|
|
32903
33774
|
CREATE TABLE IF NOT EXISTS audit (id INTEGER PRIMARY KEY AUTOINCREMENT, actor TEXT NOT NULL, organization TEXT NOT NULL, action TEXT NOT NULL, data TEXT NOT NULL, at INTEGER NOT NULL);
|
|
@@ -33160,12 +34031,14 @@ var ManagementStore = class extends DurableObject3 {
|
|
|
33160
34031
|
return { total, audit };
|
|
33161
34032
|
}
|
|
33162
34033
|
projects(organization) {
|
|
33163
|
-
|
|
33164
|
-
|
|
33165
|
-
organization
|
|
33166
|
-
|
|
33167
|
-
|
|
33168
|
-
|
|
34034
|
+
const query = "SELECT p.*,s.data AS setup FROM projects p LEFT JOIN project_setup s ON p.organization=s.organization AND p.project=s.project";
|
|
34035
|
+
return this.ctx.storage.sql.exec(
|
|
34036
|
+
query + (organization ? " WHERE p.organization=?" : "") + " ORDER BY p.name,p.project",
|
|
34037
|
+
...organization ? [organization] : []
|
|
34038
|
+
).toArray().map(({ setup, ...project }) => ({
|
|
34039
|
+
...project,
|
|
34040
|
+
...setup ? { setup: projectSetupSchema.parse(JSON.parse(setup)) } : {}
|
|
34041
|
+
}));
|
|
33169
34042
|
}
|
|
33170
34043
|
create(organization, input, actor) {
|
|
33171
34044
|
return this.ctx.storage.transactionSync(() => {
|
|
@@ -33173,7 +34046,7 @@ var ManagementStore = class extends DurableObject3 {
|
|
|
33173
34046
|
(p) => p.project === input.project
|
|
33174
34047
|
);
|
|
33175
34048
|
if (existing) {
|
|
33176
|
-
if (existing.name !== input.name)
|
|
34049
|
+
if (existing.name !== input.name || input.setup && JSON.stringify(existing.setup) !== JSON.stringify(input.setup))
|
|
33177
34050
|
throw new Error("Project ID is already in use");
|
|
33178
34051
|
return existing;
|
|
33179
34052
|
}
|
|
@@ -33183,6 +34056,13 @@ var ManagementStore = class extends DurableObject3 {
|
|
|
33183
34056
|
input.project,
|
|
33184
34057
|
input.name
|
|
33185
34058
|
);
|
|
34059
|
+
if (input.setup)
|
|
34060
|
+
this.ctx.storage.sql.exec(
|
|
34061
|
+
"INSERT INTO project_setup VALUES(?,?,?)",
|
|
34062
|
+
organization,
|
|
34063
|
+
input.project,
|
|
34064
|
+
JSON.stringify(input.setup)
|
|
34065
|
+
);
|
|
33186
34066
|
this.audit(actor, organization, "project-created", input);
|
|
33187
34067
|
return { organization, ...input };
|
|
33188
34068
|
});
|
|
@@ -33263,6 +34143,7 @@ var photo_quest_default = { code: 'var Kd=Object.defineProperty;var ee=(e,r)=>{f
|
|
|
33263
34143
|
|
|
33264
34144
|
// apps/api/src/auth-routes.ts
|
|
33265
34145
|
init_zod();
|
|
34146
|
+
init_dist();
|
|
33266
34147
|
|
|
33267
34148
|
// apps/api/src/oauth-routes.ts
|
|
33268
34149
|
init_zod();
|
|
@@ -33559,6 +34440,8 @@ async function oauthRoutes(request, env, session) {
|
|
|
33559
34440
|
|
|
33560
34441
|
// apps/api/src/auth-routes.ts
|
|
33561
34442
|
var emailFlowSchema = external_exports.object({
|
|
34443
|
+
scope: authScopeSchema,
|
|
34444
|
+
origin: external_exports.string(),
|
|
33562
34445
|
email: external_exports.string().email(),
|
|
33563
34446
|
digest: external_exports.string(),
|
|
33564
34447
|
binding: external_exports.string()
|
|
@@ -33602,6 +34485,8 @@ async function authRoutes(request, env, session) {
|
|
|
33602
34485
|
id2,
|
|
33603
34486
|
{
|
|
33604
34487
|
email: email3,
|
|
34488
|
+
scope: authScope(env),
|
|
34489
|
+
origin: request.headers.get("Origin") ?? "",
|
|
33605
34490
|
digest: await hash2(id2 + ":" + code),
|
|
33606
34491
|
binding: await hash2(binding)
|
|
33607
34492
|
},
|
|
@@ -33614,6 +34499,8 @@ async function authRoutes(request, env, session) {
|
|
|
33614
34499
|
id2,
|
|
33615
34500
|
JSON.stringify({
|
|
33616
34501
|
email: email3,
|
|
34502
|
+
scope: authScope(env),
|
|
34503
|
+
origin: request.headers.get("Origin") ?? "",
|
|
33617
34504
|
digest: await hash2(id2 + ":" + code),
|
|
33618
34505
|
binding: await hash2(binding)
|
|
33619
34506
|
})
|
|
@@ -33638,7 +34525,12 @@ async function authRoutes(request, env, session) {
|
|
|
33638
34525
|
const raw = await auth.readFlow(challenge);
|
|
33639
34526
|
if (!raw || !await auth.rateLimit("verify:" + challenge, 5, 6e5))
|
|
33640
34527
|
return fail("Code expired or too many attempts. Request another.");
|
|
33641
|
-
const
|
|
34528
|
+
const parsed = emailFlowSchema.safeParse(JSON.parse(raw));
|
|
34529
|
+
if (!parsed.success) return fail("Code expired. Request another.");
|
|
34530
|
+
const flow = parsed.data;
|
|
34531
|
+
const scope = authScope(env);
|
|
34532
|
+
if (flow.scope.organization !== scope.organization || flow.scope.project !== scope.project || flow.scope.environment !== scope.environment || flow.origin !== (request.headers.get("Origin") ?? ""))
|
|
34533
|
+
return fail("Sign-in scope changed. Request another code.");
|
|
33642
34534
|
if (flow.binding !== await hash2(cookie(request, "relay_auth_flow")) || flow.digest !== await hash2(challenge + ":" + code))
|
|
33643
34535
|
return fail("Invalid code");
|
|
33644
34536
|
if (!await auth.consumeFlow(challenge, raw))
|
|
@@ -33660,7 +34552,7 @@ async function authRoutes(request, env, session) {
|
|
|
33660
34552
|
|
|
33661
34553
|
// apps/api/src/index.ts
|
|
33662
34554
|
var gateway = {
|
|
33663
|
-
async fetch(request, env) {
|
|
34555
|
+
async fetch(request, env, identityIntegration) {
|
|
33664
34556
|
const git = await gitGateway(request, env);
|
|
33665
34557
|
if (git) return git;
|
|
33666
34558
|
const device = await deviceRoutes(request, env);
|
|
@@ -33677,7 +34569,7 @@ var gateway = {
|
|
|
33677
34569
|
const url2 = new URL(request.url);
|
|
33678
34570
|
const local = localParticipantRequest(request, env);
|
|
33679
34571
|
if (!local) {
|
|
33680
|
-
const issues = productionParticipantIssues(env);
|
|
34572
|
+
const issues = identityIntegration ? [] : productionParticipantIssues(env);
|
|
33681
34573
|
if (issues.length)
|
|
33682
34574
|
return Response.json(
|
|
33683
34575
|
{ error: "Participant access is not configured for this deployment" },
|
|
@@ -33691,13 +34583,13 @@ var gateway = {
|
|
|
33691
34583
|
{ error: "Participant project has not been provisioned" },
|
|
33692
34584
|
{ status: 503 }
|
|
33693
34585
|
);
|
|
33694
|
-
if (url2.pathname === "/v1/auth/
|
|
34586
|
+
if (url2.pathname === "/v1/auth/demo")
|
|
33695
34587
|
return Response.json({ error: "Route not found" }, { status: 404 });
|
|
33696
34588
|
}
|
|
33697
34589
|
const auth = env.AUTH.getByName("auth-v1");
|
|
33698
34590
|
const token = sessionToken(request);
|
|
33699
34591
|
const management = local && !!env.API_TOKEN && request.headers.get("Authorization") === "Bearer " + env.API_TOKEN;
|
|
33700
|
-
const resolvedSession = management ? null : token ? await auth.resolve(token) : null;
|
|
34592
|
+
const resolvedSession = management ? null : token ? await auth.resolve(token, identityIntegration) : null;
|
|
33701
34593
|
const portalScope = authScope(env);
|
|
33702
34594
|
const session = resolvedSession && (!local || !request.headers.has("Authorization")) && (resolvedSession.organization !== portalScope.organization || resolvedSession.project !== portalScope.project || resolvedSession.environment !== portalScope.environment) ? null : resolvedSession;
|
|
33703
34595
|
if (!local && session && session.principal.kind !== "member")
|
|
@@ -34067,6 +34959,11 @@ var gateway = {
|
|
|
34067
34959
|
}
|
|
34068
34960
|
}
|
|
34069
34961
|
};
|
|
34962
|
+
var PreviewGateway = class extends WorkerEntrypoint3 {
|
|
34963
|
+
async fetch(request) {
|
|
34964
|
+
return developmentPreview(request, this.env, gateway.fetch);
|
|
34965
|
+
}
|
|
34966
|
+
};
|
|
34070
34967
|
var StagingGateway = class extends WorkerEntrypoint3 {
|
|
34071
34968
|
async fetch(request) {
|
|
34072
34969
|
return await stagingRequest(request, this.env, gateway.fetch) ?? new Response("Staging not found", { status: 404 });
|
|
@@ -34074,7 +34971,11 @@ var StagingGateway = class extends WorkerEntrypoint3 {
|
|
|
34074
34971
|
};
|
|
34075
34972
|
var index_default = {
|
|
34076
34973
|
async fetch(request, env) {
|
|
34077
|
-
const response = await
|
|
34974
|
+
const response = await identityIntegrationRequest(
|
|
34975
|
+
request,
|
|
34976
|
+
env,
|
|
34977
|
+
(incoming, scoped, id2) => gateway.fetch(incoming, scoped, id2)
|
|
34978
|
+
) ?? await gateway.fetch(request, await callbackEnvironment(request, env));
|
|
34078
34979
|
const headers = new Headers(response.headers);
|
|
34079
34980
|
headers.set("Cache-Control", "no-store");
|
|
34080
34981
|
return new Response(response.body, { status: response.status, headers });
|
|
@@ -34086,6 +34987,8 @@ export {
|
|
|
34086
34987
|
HostingControl,
|
|
34087
34988
|
ManagementStore,
|
|
34088
34989
|
OrganizationLedger,
|
|
34990
|
+
PreviewGateway,
|
|
34991
|
+
PreviewSession,
|
|
34089
34992
|
StagingAuth,
|
|
34090
34993
|
StagingGateway,
|
|
34091
34994
|
StagingLedger,
|