Make MCP and runtime callbacks run with mandatory account identity and propagate it through task runs into source credential resolution. Tighten control-plane auth defaults and sanitize proxied response headers for safer direct Convex access.
Unified · Split
| @@ -26,6 +26,7 @@ import type * as control_plane_workspaces from "../control_plane/workspaces.js"; · 25 unmodified lines | |||
| 26 | import type * as credential_crypto from "../credential_crypto.js"; | 26 | import type * as credential_crypto from "../credential_crypto.js"; |
| 27 | import type * as executor from "../executor.js"; | 27 | import type * as executor from "../executor.js"; |
| 28 | import type * as http from "../http.js"; | 28 | import type * as http from "../http.js"; |
| 29 | import type * as http_security from "../http_security.js"; | ||
| 29 | import type * as mcp from "../mcp.js"; | 30 | import type * as mcp from "../mcp.js"; |
| 30 | import type * as mcp_auth from "../mcp_auth.js"; | 31 | import type * as mcp_auth from "../mcp_auth.js"; |
| 31 | import type * as run_executor from "../run_executor.js"; | 32 | import type * as run_executor from "../run_executor.js"; |
| @@ -61,6 +62,7 @@ declare const fullApi: ApiFromModules<{ · 29 unmodified lines | |||
| 61 | credential_crypto: typeof credential_crypto; | 62 | credential_crypto: typeof credential_crypto; |
| 62 | executor: typeof executor; | 63 | executor: typeof executor; |
| 63 | http: typeof http; | 64 | http: typeof http; |
| 65 | http_security: typeof http_security; | ||
| 64 | mcp: typeof mcp; | 66 | mcp: typeof mcp; |
| 65 | mcp_auth: typeof mcp_auth; | 67 | mcp_auth: typeof mcp_auth; |
| 66 | run_executor: typeof run_executor; | 68 | run_executor: typeof run_executor; |
| @@ -21,7 +21,6 @@ const providers: AuthConfig["providers"] = clientId · 20 unmodified lines | |||
| 21 | type: "customJwt", | 21 | type: "customJwt", |
| 22 | issuer: `https://api.workos.com/user_management/${clientId}`, | 22 | issuer: `https://api.workos.com/user_management/${clientId}`, |
| 23 | algorithm: "RS256", | 23 | algorithm: "RS256", |
| 24 | applicationID: clientId, | ||
| 25 | jwks: `https://api.workos.com/sso/jwks/${clientId}`, | 24 | jwks: `https://api.workos.com/sso/jwks/${clientId}`, |
| 26 | }, | 25 | }, |
| 27 | ] | 26 | ] |
| @@ -294,7 +294,7 @@ export const ConvexControlPlaneActorLive = (ctx: ActionCtx) => · 293 unmodified lines | |||
| 294 | 294 | ||
| 295 | const principal = yield* resolveAuthenticatedPrincipal(ctx); | 295 | const principal = yield* resolveAuthenticatedPrincipal(ctx); |
| 296 | 296 | ||
| 297 | const organizationMemberships = yield* runQueryEffect( | 297 | let organizationMemberships = yield* runQueryEffect( |
| 298 | "organizationMembership.list", | 298 | "organizationMembership.list", |
| 299 | () => | 299 | () => |
| 300 | ctx.runQuery(internal.control_plane.actor.listOrganizationMembershipsForActor, { | 300 | ctx.runQuery(internal.control_plane.actor.listOrganizationMembershipsForActor, { |
| @@ -302,17 +302,47 @@ export const ConvexControlPlaneActorLive = (ctx: ActionCtx) => · 1 unmodified line | |||
| 302 | }), | 302 | }), |
| 303 | ); | 303 | ); |
| 304 | 304 | ||
| 305 | const organizationIds = organizationMemberships.map( | 305 | let organizationIds = organizationMemberships.map( |
| 306 | (membership) => membership.organizationId, | 306 | (membership) => membership.organizationId, |
| 307 | ); | 307 | ); |
| 308 | 308 | ||
| 309 | const workspaces = yield* runQueryEffect("workspace.list", () => | 309 | let workspaces = yield* runQueryEffect("workspace.list", () => |
| 310 | ctx.runQuery(internal.control_plane.actor.listWorkspacesForActor, { | 310 | ctx.runQuery(internal.control_plane.actor.listWorkspacesForActor, { |
| 311 | accountId: principal.accountId, | 311 | accountId: principal.accountId, |
| 312 | organizationIds, | 312 | organizationIds, |
| 313 | }), | 313 | }), |
| 314 | ); | 314 | ); |
| 315 | 315 | ||
| 316 | if (organizationMemberships.length === 0) { | ||
| 317 | const workspaceId = workspaces[0]?.id ?? `ws_${principal.accountId}`; | ||
| 318 | |||
| 319 | yield* runQueryEffect("workspace.ensureDefault", () => | ||
| 320 | ctx.runMutation(internal.control_plane.actor.ensureWorkspaceForActor, { | ||
| 321 | workspaceId, | ||
| 322 | accountId: principal.accountId, | ||
| 323 | }), | ||
| 324 | ); | ||
| 325 | |||
| 326 | organizationMemberships = yield* runQueryEffect( | ||
| 327 | "organizationMembership.list", | ||
| 328 | () => | ||
| 329 | ctx.runQuery(internal.control_plane.actor.listOrganizationMembershipsForActor, { | ||
| 330 | accountId: principal.accountId, | ||
| 331 | }), | ||
| 332 | ); | ||
| 333 | |||
| 334 | organizationIds = organizationMemberships.map( | ||
| 335 | (membership) => membership.organizationId, | ||
| 336 | ); | ||
| 337 | |||
| 338 | workspaces = yield* runQueryEffect("workspace.list", () => | ||
| 339 | ctx.runQuery(internal.control_plane.actor.listWorkspacesForActor, { | ||
| 340 | accountId: principal.accountId, | ||
| 341 | organizationIds, | ||
| 342 | }), | ||
| 343 | ); | ||
| 344 | } | ||
| 345 | |||
| 316 | const workspaceMemberships = workspaces.flatMap((workspace) => | 346 | const workspaceMemberships = workspaces.flatMap((workspace) => |
| 317 | deriveWorkspaceMembershipsForPrincipal({ | 347 | deriveWorkspaceMembershipsForPrincipal({ |
| 318 | principalAccountId: principal.accountId, | 348 | principalAccountId: principal.accountId, |
| @@ -1410,6 +1410,7 @@ describe("Convex executor and control-plane", () => { · 1409 unmodified lines | |||
| 1410 | t.mutation(runtimeInternal.task_runs.startTaskRun, { | 1410 | t.mutation(runtimeInternal.task_runs.startTaskRun, { |
| 1411 | workspaceId: "ws_1", | 1411 | workspaceId: "ws_1", |
| 1412 | runId: "run_approval_1", | 1412 | runId: "run_approval_1", |
| 1413 | accountId: "acct_1", | ||
| 1413 | }), | 1414 | }), |
| 1414 | ); | 1415 | ); |
| 1415 | 1416 | ||
| @@ -19,19 +19,11 @@ import { · 18 unmodified lines | |||
| 19 | } from "./mcp_auth"; | 19 | } from "./mcp_auth"; |
| 20 | import { createConvexSourceToolRegistry } from "./source_tool_registry"; | 20 | import { createConvexSourceToolRegistry } from "./source_tool_registry"; |
| 21 | 21 | ||
| 22 | const readConfiguredWorkspaceId = (value: string | undefined): string => { | ||
| 23 | const normalized = value?.trim(); | ||
| 24 | return normalized && normalized.length > 0 ? normalized : "ws_local"; | ||
| 25 | }; | ||
| 26 | |||
| 27 | const readWorkspaceIdFromRequest = (request: Request): string | null => { | 22 | const readWorkspaceIdFromRequest = (request: Request): string | null => { |
| 28 | const value = new URL(request.url).searchParams.get("workspaceId")?.trim(); | 23 | const value = new URL(request.url).searchParams.get("workspaceId")?.trim(); |
| 29 | return value && value.length > 0 ? value : null; | 24 | return value && value.length > 0 ? value : null; |
| 30 | }; | 25 | }; |
| 31 | 26 | ||
| 32 | const readRequestedWorkspaceId = (request: Request, fallbackWorkspaceId: string): string => | ||
| 33 | readWorkspaceIdFromRequest(request) ?? fallbackWorkspaceId; | ||
| 34 | |||
| 35 | const readToolExposureModeFromRequest = ( | 27 | const readToolExposureModeFromRequest = ( |
| 36 | request: Request, | 28 | request: Request, |
| 37 | fallbackMode: ExecuteToolExposureMode, | 29 | fallbackMode: ExecuteToolExposureMode, |
| @@ -96,7 +88,6 @@ const hasWorkspaceAccess = async ( · 58 unmodified lines | |||
| 96 | } | 88 | } |
| 97 | }; | 89 | }; |
| 98 | 90 | ||
| 99 | const fallbackWorkspaceId = readConfiguredWorkspaceId(process.env.CONVEX_WORKSPACE_ID); | ||
| 100 | const defaultToolExposureMode = | 91 | const defaultToolExposureMode = |
| 101 | parseExecuteToolExposureMode(process.env.CONVEX_TOOL_EXPOSURE_MODE) ?? | 92 | parseExecuteToolExposureMode(process.env.CONVEX_TOOL_EXPOSURE_MODE) ?? |
| 102 | defaultExecuteToolExposureMode; | 93 | defaultExecuteToolExposureMode; |
| @@ -104,45 +95,41 @@ const runtimeInternal = internal as any; · 1 unmodified line | |||
| 104 | 95 | ||
| 105 | export const mcpHandler = httpAction(async (ctx, request) => { | 96 | export const mcpHandler = httpAction(async (ctx, request) => { |
| 106 | const mcpAuthConfig = getMcpAuthConfig(); | 97 | const mcpAuthConfig = getMcpAuthConfig(); |
| 107 | const requestWorkspaceId = readWorkspaceIdFromRequest(request); | 98 | const workspaceId = readWorkspaceIdFromRequest(request); |
| 108 | const workspaceId = readRequestedWorkspaceId(request, fallbackWorkspaceId); | ||
| 109 | let accountId: string | null = null; | ||
| 110 | 99 | ||
| 111 | if (!mcpAuthConfig.enabled) { | 100 | if (!mcpAuthConfig.enabled) { |
| 112 | if (mcpAuthConfig.required) { | 101 | return Response.json( |
| 113 | return Response.json( | 102 | { |
| 114 | { | 103 | error: "MCP OAuth must be configured", |
| 115 | error: "MCP OAuth must be configured for cloud deployments", | 104 | }, |
| 116 | }, | 105 | { status: 503 }, |
| 117 | { status: 503 }, | 106 | ); |
| 118 | ); | 107 | } |
| 119 | } | ||
| 120 | } else { | ||
| 121 | const auth = await verifyMcpToken(request, mcpAuthConfig); | ||
| 122 | if (!auth) { | ||
| 123 | return unauthorizedMcpResponse(request, "No valid bearer token provided."); | ||
| 124 | } | ||
| 125 | 108 | ||
| 126 | accountId = auth.subject; | 109 | const auth = await verifyMcpToken(request, mcpAuthConfig); |
| 110 | if (!auth) { | ||
| 111 | return unauthorizedMcpResponse(request, "No valid bearer token provided."); | ||
| 112 | } | ||
| 127 | 113 | ||
| 128 | if (!requestWorkspaceId) { | 114 | if (!workspaceId) { |
| 129 | return Response.json( | 115 | return Response.json( |
| 130 | { | 116 | { |
| 131 | error: "workspaceId query parameter is required when MCP OAuth is enabled", | 117 | error: "workspaceId query parameter is required", |
| 132 | }, | 118 | }, |
| 133 | { status: 400 }, | 119 | { status: 400 }, |
| 134 | ); | 120 | ); |
| 135 | } | 121 | } |
| 136 | 122 | ||
| 137 | const authorized = await hasWorkspaceAccess(ctx, requestWorkspaceId, auth.subject); | 123 | const accountId = auth.subject; |
| 138 | if (!authorized) { | 124 | |
| 139 | return Response.json( | 125 | const authorized = await hasWorkspaceAccess(ctx, workspaceId, accountId); |
| 140 | { | 126 | if (!authorized) { |
| 141 | error: "Workspace authorization failed", | 127 | return Response.json( |
| 142 | }, | 128 | { |
| 143 | { status: 403 }, | 129 | error: "Workspace authorization failed", |
| 144 | ); | 130 | }, |
| 145 | } | 131 | { status: 403 }, |
| 132 | ); | ||
| 146 | } | 133 | } |
| 147 | 134 | ||
| 148 | const toolRegistry = createConvexSourceToolRegistry(ctx, workspaceId, { | 135 | const toolRegistry = createConvexSourceToolRegistry(ctx, workspaceId, { |
| @@ -88,7 +88,7 @@ const decodeToolCallRequest = (request: Request): Effect.Effect<RuntimeToolCallR · 87 unmodified lines | |||
| 88 | const resolveWorkspaceIdForToolCall = ( | 88 | const resolveWorkspaceIdForToolCall = ( |
| 89 | ctx: ActionCtx, | 89 | ctx: ActionCtx, |
| 90 | input: RuntimeToolCallRequest, | 90 | input: RuntimeToolCallRequest, |
| 91 | ): Effect.Effect<{ workspaceId: string; accountId: string | null }, RuntimeToolCallBadRequestError> => | 91 | ): Effect.Effect<{ workspaceId: string; accountId: string }, RuntimeToolCallBadRequestError> => |
| 92 | Effect.gen(function* () { | 92 | Effect.gen(function* () { |
| 93 | const runContext = yield* Effect.tryPromise({ | 93 | const runContext = yield* Effect.tryPromise({ |
| 94 | try: () => | 94 | try: () => |
| @@ -103,12 +103,17 @@ const resolveWorkspaceIdForToolCall = ( · 8 unmodified lines | |||
| 103 | }); | 103 | }); |
| 104 | 104 | ||
| 105 | if (runContext && runContext.workspaceId.trim().length > 0) { | 105 | if (runContext && runContext.workspaceId.trim().length > 0) { |
| 106 | const accountId = runContext.accountId.trim(); | ||
| 107 | if (accountId.length === 0) { | ||
| 108 | return yield* new RuntimeToolCallBadRequestError({ | ||
| 109 | message: "Runtime callback run is missing account identity", | ||
| 110 | details: `runId=${input.runId}`, | ||
| 111 | }); | ||
| 112 | } | ||
| 113 | |||
| 106 | return { | 114 | return { |
| 107 | workspaceId: runContext.workspaceId.trim(), | 115 | workspaceId: runContext.workspaceId.trim(), |
| 108 | accountId: | 116 | accountId, |
| 109 | typeof runContext.accountId === "string" && runContext.accountId.trim().length > 0 | ||
| 110 | ? runContext.accountId.trim() | ||
| 111 | : null, | ||
| 112 | }; | 117 | }; |
| 113 | } | 118 | } |
| 114 | 119 | ||
| @@ -268,21 +268,15 @@ const mergeHeaders = (...sets: ReadonlyArray<Record<string, string>>): Record<st · 267 unmodified lines | |||
| 268 | const resolveSourceCredentialHeadersForInvocation = async ( | 268 | const resolveSourceCredentialHeadersForInvocation = async ( |
| 269 | ctx: ActionCtx, | 269 | ctx: ActionCtx, |
| 270 | source: Source, | 270 | source: Source, |
| 271 | accountId: string | null, | 271 | accountId: string, |
| 272 | ): Promise<Record<string, string>> => { | 272 | ): Promise<Record<string, string>> => { |
| 273 | try { | 273 | try { |
| 274 | const accountInput = accountId | ||
| 275 | ? { | ||
| 276 | accountId, | ||
| 277 | } | ||
| 278 | : {}; | ||
| 279 | |||
| 280 | const resolved = (await ctx.runAction( | 274 | const resolved = (await ctx.runAction( |
| 281 | runtimeInternal.control_plane.credentials.resolveSourceCredentialHeaders, | 275 | runtimeInternal.control_plane.credentials.resolveSourceCredentialHeaders, |
| 282 | { | 276 | { |
| 283 | workspaceId: source.workspaceId, | 277 | workspaceId: source.workspaceId, |
| 284 | sourceId: source.id, | 278 | sourceId: source.id, |
| 285 | ...accountInput, | 279 | accountId, |
| 286 | }, | 280 | }, |
| 287 | )) as { headers?: unknown }; | 281 | )) as { headers?: unknown }; |
| 288 | 282 | ||
| @@ -306,7 +300,7 @@ const resolveSourceCredentialHeadersForInvocation = async ( · 17 unmodified lines | |||
| 306 | const resolveSourceHeadersForInvocation = async ( | 300 | const resolveSourceHeadersForInvocation = async ( |
| 307 | ctx: ActionCtx, | 301 | ctx: ActionCtx, |
| 308 | source: Source, | 302 | source: Source, |
| 309 | accountId: string | null, | 303 | accountId: string, |
| 310 | ): Promise<Record<string, string>> => { | 304 | ): Promise<Record<string, string>> => { |
| 311 | const config = parseSourceConfig(source); | 305 | const config = parseSourceConfig(source); |
| 312 | 306 | ||
| @@ -1047,7 +1041,7 @@ const invokeToolWithRegistry = ( · 734 unmodified lines | |||
| 1047 | source: Source, | 1041 | source: Source, |
| 1048 | artifactTool: ArtifactToolRow, | 1042 | artifactTool: ArtifactToolRow, |
| 1049 | args: Record<string, unknown>, | 1043 | args: Record<string, unknown>, |
| 1050 | accountId: string | null, | 1044 | accountId: string, |
| 1051 | ): Effect.Effect<{ output: unknown; isError: boolean }, RuntimeAdapterError> => | 1045 | ): Effect.Effect<{ output: unknown; isError: boolean }, RuntimeAdapterError> => |
| 1052 | Effect.gen(function* () { | 1046 | Effect.gen(function* () { |
| 1053 | const openApiProvider = makeOpenApiToolProvider(); | 1047 | const openApiProvider = makeOpenApiToolProvider(); |
| @@ -1583,21 +1577,21 @@ const describeSource = (row: { · 529 unmodified lines | |||
| 1583 | }): string => `${row.sourceKind.toUpperCase()} source: ${row.sourceName}`; | 1577 | }): string => `${row.sourceKind.toUpperCase()} source: ${row.sourceName}`; |
| 1584 | 1578 | ||
| 1585 | export type ConvexSourceToolRegistryOptions = { | 1579 | export type ConvexSourceToolRegistryOptions = { |
| 1580 | accountId: string; | ||
| 1586 | requireToolApprovals?: boolean; | 1581 | requireToolApprovals?: boolean; |
| 1587 | approvalRetryAfterMs?: number; | 1582 | approvalRetryAfterMs?: number; |
| 1588 | accountId?: string | null; | ||
| 1589 | }; | 1583 | }; |
| 1590 | 1584 | ||
| 1591 | export const createConvexSourceToolRegistry = ( | 1585 | export const createConvexSourceToolRegistry = ( |
| 1592 | ctx: ActionCtx, | 1586 | ctx: ActionCtx, |
| 1593 | workspaceId: string, | 1587 | workspaceId: string, |
| 1594 | options: ConvexSourceToolRegistryOptions = {}, | 1588 | options: ConvexSourceToolRegistryOptions, |
| 1595 | ): ToolRegistry => { | 1589 | ): ToolRegistry => { |
| 1596 | const requireApprovals = options.requireToolApprovals ?? requireToolApprovalsByDefault; | 1590 | const requireApprovals = options.requireToolApprovals ?? requireToolApprovalsByDefault; |
| 1597 | const accountId = | 1591 | const accountId = options.accountId.trim(); |
| 1598 | typeof options.accountId === "string" && options.accountId.trim().length > 0 | 1592 | if (accountId.length === 0) { |
| 1599 | ? options.accountId.trim() | 1593 | throw new Error("Tool registry accountId is required"); |
| 1600 | : null; | 1594 | } |
| 1601 | const approvalRetryAfterMs = | 1595 | const approvalRetryAfterMs = |
| 1602 | typeof options.approvalRetryAfterMs === "number" && | 1596 | typeof options.approvalRetryAfterMs === "number" && |
| 1603 | Number.isFinite(options.approvalRetryAfterMs) && | 1597 | Number.isFinite(options.approvalRetryAfterMs) && |
| @@ -14,7 +14,7 @@ export const startTaskRun = internalMutation({ · 13 unmodified lines | |||
| 14 | args: { | 14 | args: { |
| 15 | workspaceId: v.string(), | 15 | workspaceId: v.string(), |
| 16 | runId: v.string(), | 16 | runId: v.string(), |
| 17 | accountId: v.optional(v.union(v.string(), v.null())), | 17 | accountId: v.string(), |
| 18 | sessionId: v.optional(v.string()), | 18 | sessionId: v.optional(v.string()), |
| 19 | runtimeId: v.optional(v.string()), | 19 | runtimeId: v.optional(v.string()), |
| 20 | codeHash: v.optional(v.string()), | 20 | codeHash: v.optional(v.string()), |
| @@ -29,13 +29,18 @@ export const startTaskRun = internalMutation({ · 8 unmodified lines | |||
| 29 | if (existing.workspaceId !== args.workspaceId) { | 29 | if (existing.workspaceId !== args.workspaceId) { |
| 30 | throw new Error(`Task run workspace mismatch: ${args.runId}`); | 30 | throw new Error(`Task run workspace mismatch: ${args.runId}`); |
| 31 | } | 31 | } |
| 32 | |||
| 33 | if (existing.accountId !== args.accountId) { | ||
| 34 | throw new Error(`Task run account mismatch: ${args.runId}`); | ||
| 35 | } | ||
| 36 | |||
| 32 | return; | 37 | return; |
| 33 | } | 38 | } |
| 34 | 39 | ||
| 35 | const taskRun = { | 40 | const taskRun = { |
| 36 | id: args.runId, | 41 | id: args.runId, |
| 37 | workspaceId: args.workspaceId, | 42 | workspaceId: args.workspaceId, |
| 38 | accountId: args.accountId ?? null, | 43 | accountId: args.accountId, |
| 39 | sessionId: args.sessionId ?? "session_runtime", | 44 | sessionId: args.sessionId ?? "session_runtime", |
| 40 | runtimeId: args.runtimeId ?? "runtime", | 45 | runtimeId: args.runtimeId ?? "runtime", |
| 41 | codeHash: args.codeHash ?? "runtime", | 46 | codeHash: args.codeHash ?? "runtime", |
| @@ -83,7 +88,7 @@ export const getTaskRunContext = internalQuery({ · 41 unmodified lines | |||
| 83 | args: { | 88 | args: { |
| 84 | runId: v.string(), | 89 | runId: v.string(), |
| 85 | }, | 90 | }, |
| 86 | handler: async (ctx, args): Promise<{ workspaceId: string; accountId: string | null } | null> => { | 91 | handler: async (ctx, args): Promise<{ workspaceId: string; accountId: string } | null> => { |
| 87 | const existing = await ctx.db | 92 | const existing = await ctx.db |
| 88 | .query("taskRuns") | 93 | .query("taskRuns") |
| 89 | .withIndex("by_domainId", (q) => q.eq("id", args.runId)) | 94 | .withIndex("by_domainId", (q) => q.eq("id", args.runId)) |
| @@ -95,7 +100,7 @@ export const getTaskRunContext = internalQuery({ · 5 unmodified lines | |||
| 95 | 100 | ||
| 96 | return { | 101 | return { |
| 97 | workspaceId: existing.workspaceId, | 102 | workspaceId: existing.workspaceId, |
| 98 | accountId: typeof existing.accountId === "string" ? existing.accountId : null, | 103 | accountId: existing.accountId, |
| 99 | }; | 104 | }; |
| 100 | }, | 105 | }, |
| 101 | }); | 106 | }); |
| @@ -57,9 +57,29 @@ const buildUpstreamHeaders = ( · 56 unmodified lines | |||
| 57 | } | 57 | } |
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | headers.set("accept-encoding", "identity"); | ||
| 61 | |||
| 60 | return headers; | 62 | return headers; |
| 61 | }; | 63 | }; |
| 62 | 64 | ||
| 65 | const responseHeadersToStrip = [ | ||
| 66 | "content-encoding", | ||
| 67 | "content-length", | ||
| 68 | "transfer-encoding", | ||
| 69 | "connection", | ||
| 70 | "keep-alive", | ||
| 71 | ] as const; | ||
| 72 | |||
| 73 | const sanitizeUpstreamResponseHeaders = (headers: Headers): Headers => { | ||
| 74 | const sanitized = new Headers(headers); | ||
| 75 | |||
| 76 | for (const name of responseHeadersToStrip) { | ||
| 77 | sanitized.delete(name); | ||
| 78 | } | ||
| 79 | |||
| 80 | return sanitized; | ||
| 81 | }; | ||
| 82 | |||
| 63 | const resolveUpstreamUrl = ( | 83 | const resolveUpstreamUrl = ( |
| 64 | request: NextRequest, | 84 | request: NextRequest, |
| 65 | pathSegments: ReadonlyArray<string>, | 85 | pathSegments: ReadonlyArray<string>, |
| @@ -118,7 +138,7 @@ const handle = async (request: NextRequest, context: RouteContext): Promise<Resp · 52 unmodified lines | |||
| 118 | return new Response(upstreamResponse.body, { | 138 | return new Response(upstreamResponse.body, { |
| 119 | status: upstreamResponse.status, | 139 | status: upstreamResponse.status, |
| 120 | statusText: upstreamResponse.statusText, | 140 | statusText: upstreamResponse.statusText, |
| 121 | headers: upstreamResponse.headers, | 141 | headers: sanitizeUpstreamResponseHeaders(upstreamResponse.headers), |
| 122 | }); | 142 | }); |
| 123 | }; | 143 | }; |
| 124 | 144 | ||
| @@ -9,19 +9,15 @@ const trim = (value: string | undefined): string | undefined => { · 8 unmodified lines | |||
| 9 | return candidate && candidate.length > 0 ? candidate : undefined; | 9 | return candidate && candidate.length > 0 ? candidate : undefined; |
| 10 | }; | 10 | }; |
| 11 | 11 | ||
| 12 | const publicControlPlaneBaseUrl = | 12 | const publicControlPlaneBaseUrl = trim(process.env.NEXT_PUBLIC_CONTROL_PLANE_BASE_URL); |
| 13 | trim(process.env.NEXT_PUBLIC_CONTROL_PLANE_BASE_URL) | ||
| 14 | ?? trim(process.env.CONTROL_PLANE_SERVER_BASE_URL) | ||
| 15 | ?? trim(process.env.CONTROL_PLANE_UPSTREAM_URL) | ||
| 16 | ?? trim(process.env.NEXT_PUBLIC_CONVEX_URL) | ||
| 17 | ?? trim(process.env.CONVEX_URL) | ||
| 18 | ?? ""; | ||
| 19 | 13 | ||
| 20 | const nextConfig: NextConfig = { | 14 | const nextConfig: NextConfig = { |
| 21 | outputFileTracingRoot: resolve(appDir, "../.."), | 15 | outputFileTracingRoot: resolve(appDir, "../.."), |
| 22 | env: { | 16 | env: publicControlPlaneBaseUrl |
| 23 | NEXT_PUBLIC_CONTROL_PLANE_BASE_URL: publicControlPlaneBaseUrl, | 17 | ? { |
| 24 | }, | 18 | NEXT_PUBLIC_CONTROL_PLANE_BASE_URL: publicControlPlaneBaseUrl, |
| 19 | } | ||
| 20 | : {}, | ||
| 25 | }; | 21 | }; |
| 26 | 22 | ||
| 27 | export default nextConfig; | 23 | export default nextConfig; |
| @@ -7,7 +7,7 @@ import { AccountIdSchema, TaskRunIdSchema, WorkspaceIdSchema } from "../ids"; · 6 unmodified lines | |||
| 7 | export const TaskRunSchema = Schema.Struct({ | 7 | export const TaskRunSchema = Schema.Struct({ |
| 8 | id: TaskRunIdSchema, | 8 | id: TaskRunIdSchema, |
| 9 | workspaceId: WorkspaceIdSchema, | 9 | workspaceId: WorkspaceIdSchema, |
| 10 | accountId: Schema.NullOr(AccountIdSchema), | 10 | accountId: AccountIdSchema, |
| 11 | sessionId: Schema.String, | 11 | sessionId: Schema.String, |
| 12 | runtimeId: Schema.String, | 12 | runtimeId: Schema.String, |
| 13 | codeHash: Schema.String, | 13 | codeHash: Schema.String, |