@opengeni/api-router 2.5.0 → 2.6.4-canary.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/app.d.ts +2 -1
- package/dist/app.js +3 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +5 -1
- package/dist/auth/managed-auth.d.ts +24 -1
- package/dist/{chunk-QESX7HDK.js → chunk-UOI7KAP3.js} +3240 -791
- package/dist/chunk-UOI7KAP3.js.map +1 -0
- package/dist/http/sse.d.ts +9 -0
- package/dist/index.js +117 -5
- package/dist/index.js.map +1 -1
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +1 -1
- package/dist/mcp/company-profile-agent-admin.d.ts +6 -6
- package/dist/mcp/remember.d.ts +2 -2
- package/dist/mcp/scheduled-task-view.d.ts +3 -3
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/session-view.d.ts +1 -0
- package/dist/mcp/session-wait.d.ts +19 -0
- package/dist/model-catalog.d.ts +5 -31
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/browser-sessions.d.ts +1 -0
- package/dist/routes/computer-sessions.d.ts +12 -0
- package/dist/routes/managed-auth-session-sets.d.ts +7 -0
- package/dist/routes/workspaces.d.ts +1 -1
- package/dist/sandbox/metrics-ingestion.d.ts +16 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/package.json +19 -19
- package/src/app.ts +206 -29
- package/src/auth/managed-auth-attempt-context.ts +40 -3
- package/src/auth/managed-auth-session-adapter.ts +1 -0
- package/src/auth/managed-auth.ts +164 -4
- package/src/http/sse.ts +279 -45
- package/src/index.ts +13 -1
- package/src/integrations/oauth-client.ts +8 -1
- package/src/integrations/provider-oauth.ts +12 -2
- package/src/integrations/slack-interactions.ts +77 -37
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/company-brain-governed-writes.ts +38 -23
- package/src/mcp/company-profile-agent-admin.ts +7 -7
- package/src/mcp/remember.ts +19 -8
- package/src/mcp/server.ts +328 -27
- package/src/mcp/session-wait.ts +56 -8
- package/src/model-catalog.ts +45 -337
- package/src/routes/api-integrations.ts +2 -2
- package/src/routes/api-keys.ts +149 -7
- package/src/routes/automations.ts +186 -19
- package/src/routes/browser-sessions.ts +10 -2
- package/src/routes/capabilities.ts +3 -3
- package/src/routes/codex.ts +483 -74
- package/src/routes/company-profile.ts +64 -0
- package/src/routes/computer-sessions.ts +103 -1
- package/src/routes/connections.ts +271 -16
- package/src/routes/github.ts +0 -8
- package/src/routes/integration-facets.ts +8 -5
- package/src/routes/interaction-resources.ts +7 -1
- package/src/routes/managed-auth-session-sets.ts +199 -2
- package/src/routes/organization-memberships.ts +28 -8
- package/src/routes/packs.ts +5 -5
- package/src/routes/plugins.ts +2 -2
- package/src/routes/pr-review.ts +80 -4
- package/src/routes/scheduled-tasks.ts +47 -11
- package/src/routes/sessions.ts +75 -53
- package/src/routes/skills.ts +3 -3
- package/src/routes/supergrok.ts +3 -2
- package/src/routes/workspaces.ts +665 -56
- package/src/sandbox/channel-a.ts +10 -4
- package/src/sandbox/machines.ts +13 -6
- package/src/sandbox/metrics-ingestion.ts +157 -3
- package/src/sandbox/viewer.ts +20 -1
- package/src/workspace-delete-observability.ts +75 -0
- package/dist/chunk-QESX7HDK.js.map +0 -1
package/src/app.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
withSessionRlsActorContext,
|
|
45
45
|
} from "@opengeni/db";
|
|
46
46
|
import { requireSessionEventDurableFanoutCapability } from "@opengeni/events";
|
|
47
|
+
import { githubAppBotIdentityWarnings } from "@opengeni/github";
|
|
47
48
|
import { createObservability } from "@opengeni/observability";
|
|
48
49
|
import { createObjectStorage } from "@opengeni/storage";
|
|
49
50
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
@@ -65,19 +66,35 @@ import {
|
|
|
65
66
|
requireLiveAgentAttemptAuthorization,
|
|
66
67
|
requirePermission,
|
|
67
68
|
releaseManagedAuthRequestActorLease,
|
|
69
|
+
resolveCatalogSettings,
|
|
70
|
+
resolveWorkspaceCatalogSettings,
|
|
68
71
|
validateManagedAuthRequestActorLease,
|
|
69
72
|
requireSessionAuthorization,
|
|
70
73
|
SessionAuthorizationDeniedError,
|
|
71
74
|
SessionAuthorizationUnavailableError,
|
|
72
75
|
} from "@opengeni/core";
|
|
73
|
-
import { createManagedAuth } from "./auth/managed-auth";
|
|
74
76
|
import {
|
|
77
|
+
createManagedAuth,
|
|
78
|
+
isolatedManagedAuthOAuthCallbackRequest,
|
|
79
|
+
managedAuthOAuthReturnMatches,
|
|
80
|
+
resolveManagedAuthOAuthAttempt,
|
|
81
|
+
} from "./auth/managed-auth";
|
|
82
|
+
import {
|
|
83
|
+
adoptManagedAuthSession,
|
|
75
84
|
MANAGED_AUTH_SESSION_SET_COOKIE,
|
|
76
85
|
ManagedAuthActorChangeError,
|
|
86
|
+
managedAuthCsrfHash,
|
|
87
|
+
managedAuthDerivedUuid,
|
|
88
|
+
managedAuthSecretRequestDigest,
|
|
77
89
|
managedAuthSha256,
|
|
78
90
|
} from "@opengeni/core/managed-auth-session-sets";
|
|
79
91
|
import { createBetterAuthSessionAdapter } from "./auth/managed-auth-session-adapter";
|
|
80
|
-
import {
|
|
92
|
+
import {
|
|
93
|
+
currentManagedAuthCreatedSessionId,
|
|
94
|
+
runManagedAuthAttempt,
|
|
95
|
+
runManagedAuthDiscardedProviderSession,
|
|
96
|
+
runManagedAuthProvider,
|
|
97
|
+
} from "./auth/managed-auth-attempt-context";
|
|
81
98
|
import { createManagedEmailTransport } from "./auth/managed-email";
|
|
82
99
|
import { assertManagedEmailTransportMetadata } from "./auth/organization-user-setup";
|
|
83
100
|
import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
|
|
@@ -207,6 +224,22 @@ export function createApp(deps: AppDependencies): Hono {
|
|
|
207
224
|
return createAppComposition(deps).app;
|
|
208
225
|
}
|
|
209
226
|
|
|
227
|
+
export async function resolveWorkspaceMcpRouteDeps(
|
|
228
|
+
routeDeps: ApiRouteDeps,
|
|
229
|
+
grant: AccessGrant,
|
|
230
|
+
): Promise<ApiRouteDeps> {
|
|
231
|
+
const catalogSourceSettings = routeDeps.catalogSourceSettings ?? routeDeps.settings;
|
|
232
|
+
const resolvedCatalog = await resolveWorkspaceCatalogSettings(
|
|
233
|
+
routeDeps.db,
|
|
234
|
+
catalogSourceSettings,
|
|
235
|
+
{
|
|
236
|
+
accountId: grant.accountId,
|
|
237
|
+
workspaceId: grant.workspaceId,
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
return { ...routeDeps, catalogSourceSettings, settings: resolvedCatalog.settings };
|
|
241
|
+
}
|
|
242
|
+
|
|
210
243
|
export function createAppComposition(deps: AppDependencies): {
|
|
211
244
|
app: Hono;
|
|
212
245
|
routeDeps: ApiRouteDeps;
|
|
@@ -343,6 +376,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
343
376
|
: deps.transcriptionSegmenter;
|
|
344
377
|
const routeDeps: ApiRouteDeps = {
|
|
345
378
|
...deps,
|
|
379
|
+
resolveCatalogSettings: () => resolveCatalogSettings(deps.db, deps.settings),
|
|
346
380
|
observability,
|
|
347
381
|
githubStateSecret:
|
|
348
382
|
deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
|
|
@@ -579,26 +613,111 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
579
613
|
registerManagedAuthSessionSetRoutes(app, routeDeps);
|
|
580
614
|
if (managedAuth) {
|
|
581
615
|
app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
|
|
616
|
+
const pathname = new URL(c.req.url).pathname;
|
|
617
|
+
const oauthCallbackProvider = managedAuthOAuthCallbackProvider(pathname);
|
|
582
618
|
if (deps.settings.managedAuthSessionSetMode === "legacy") {
|
|
583
|
-
return
|
|
619
|
+
return oauthCallbackProvider
|
|
620
|
+
? await runManagedAuthProvider(
|
|
621
|
+
oauthCallbackProvider,
|
|
622
|
+
async () => await managedAuth.handler(c.req.raw),
|
|
623
|
+
)
|
|
624
|
+
: await managedAuth.handler(c.req.raw);
|
|
584
625
|
}
|
|
585
|
-
requireManagedAuthProviderRouteAllowed(c.req.method,
|
|
626
|
+
requireManagedAuthProviderRouteAllowed(c.req.method, pathname);
|
|
586
627
|
// Provider authentication/recovery is isolated from whichever actor the
|
|
587
628
|
// browser currently renders. Selected-session capabilities are all
|
|
588
629
|
// product-owned above this wildcard and generation/epoch fenced.
|
|
589
|
-
const headers = new Headers(c.req.raw.headers);
|
|
590
|
-
headers.delete("cookie");
|
|
591
|
-
headers.delete("authorization");
|
|
592
|
-
headers.delete("x-forwarded-user");
|
|
593
|
-
const providerRequest = new Request(c.req.raw, { headers });
|
|
594
630
|
const authority = getCookie(c, MANAGED_AUTH_SESSION_SET_COOKIE);
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
631
|
+
let providerResponse: Response;
|
|
632
|
+
let preserveCookieNames: readonly string[] | undefined;
|
|
633
|
+
if (oauthCallbackProvider) {
|
|
634
|
+
const attempt = await resolveManagedAuthOAuthAttempt(
|
|
635
|
+
managedAuth,
|
|
636
|
+
c.req.raw,
|
|
637
|
+
oauthCallbackProvider,
|
|
638
|
+
deps.settings.publicBaseUrl!,
|
|
639
|
+
);
|
|
640
|
+
if (!attempt || !authority || attempt.authorityHash !== managedAuthSha256(authority)) {
|
|
641
|
+
throw new HTTPException(409, { message: "provider_route_blocked" });
|
|
642
|
+
}
|
|
643
|
+
const isolated = await isolatedManagedAuthOAuthCallbackRequest(managedAuth, c.req.raw);
|
|
644
|
+
preserveCookieNames = [isolated.stateCookieName];
|
|
645
|
+
const handled = await runManagedAuthAttempt(
|
|
646
|
+
attempt.transactionId,
|
|
647
|
+
oauthCallbackProvider,
|
|
648
|
+
async () => {
|
|
649
|
+
const response = await managedAuth.handler(isolated.request);
|
|
650
|
+
return {
|
|
651
|
+
response,
|
|
652
|
+
authSessionId: currentManagedAuthCreatedSessionId(),
|
|
653
|
+
};
|
|
654
|
+
},
|
|
655
|
+
);
|
|
656
|
+
providerResponse = handled.response;
|
|
657
|
+
if (
|
|
658
|
+
!handled.authSessionId &&
|
|
659
|
+
managedAuthOAuthReturnMatches(
|
|
660
|
+
providerResponse.headers.get("location") ?? "",
|
|
661
|
+
new URL(deps.settings.publicBaseUrl!).origin,
|
|
662
|
+
attempt.transactionId,
|
|
663
|
+
"complete",
|
|
600
664
|
)
|
|
601
|
-
|
|
665
|
+
) {
|
|
666
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl!);
|
|
667
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
668
|
+
location.searchParams.set("social", "error");
|
|
669
|
+
providerResponse = Response.redirect(location, 302);
|
|
670
|
+
} else if (handled.authSessionId) {
|
|
671
|
+
try {
|
|
672
|
+
await adoptManagedAuthSession({
|
|
673
|
+
db: deps.db,
|
|
674
|
+
adapter: managedAuthSessionAdapter!,
|
|
675
|
+
authority,
|
|
676
|
+
authorityHash: attempt.authorityHash,
|
|
677
|
+
csrfHash: managedAuthCsrfHash(authority),
|
|
678
|
+
operationId: managedAuthDerivedUuid(
|
|
679
|
+
"opengeni:managed-auth:social-completion",
|
|
680
|
+
`${attempt.transactionId}:${attempt.provider}`,
|
|
681
|
+
),
|
|
682
|
+
requestDigest: managedAuthSecretRequestDigest(deps.settings.betterAuthSecret!, {
|
|
683
|
+
operation: "social_completion",
|
|
684
|
+
transactionId: attempt.transactionId,
|
|
685
|
+
provider: attempt.provider,
|
|
686
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
687
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
688
|
+
}),
|
|
689
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
690
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
691
|
+
transactionId: attempt.transactionId,
|
|
692
|
+
transactionSecretHash: attempt.transactionSecretHash,
|
|
693
|
+
authSessionId: handled.authSessionId,
|
|
694
|
+
mode: deps.settings.managedAuthSessionSetMode,
|
|
695
|
+
});
|
|
696
|
+
} catch {
|
|
697
|
+
// adoptManagedAuthSession revokes only when durable reconciliation
|
|
698
|
+
// proves that completion did not commit. An uncertain outcome may
|
|
699
|
+
// already point the selected slot at this session, so the callback
|
|
700
|
+
// must not independently revoke it.
|
|
701
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl!);
|
|
702
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
703
|
+
location.searchParams.set("social", "error");
|
|
704
|
+
providerResponse = Response.redirect(location, 302);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
const headers = new Headers(c.req.raw.headers);
|
|
709
|
+
headers.delete("cookie");
|
|
710
|
+
headers.delete("authorization");
|
|
711
|
+
headers.delete("x-forwarded-user");
|
|
712
|
+
const providerRequest = new Request(c.req.raw, { headers });
|
|
713
|
+
const discardProviderSession =
|
|
714
|
+
deps.settings.managedAuthSessionSetMode === "broker" || authority !== undefined;
|
|
715
|
+
providerResponse = discardProviderSession
|
|
716
|
+
? await runManagedAuthDiscardedProviderSession(
|
|
717
|
+
async () => await managedAuth.handler(providerRequest),
|
|
718
|
+
)
|
|
719
|
+
: await managedAuth.handler(providerRequest);
|
|
720
|
+
}
|
|
602
721
|
let replacementCookies: readonly string[] | undefined;
|
|
603
722
|
if (deps.settings.managedAuthSessionSetMode === "broker") {
|
|
604
723
|
replacementCookies = await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
|
|
@@ -628,19 +747,24 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
628
747
|
}
|
|
629
748
|
}
|
|
630
749
|
}
|
|
631
|
-
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
750
|
+
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
751
|
+
replacementCookies,
|
|
752
|
+
preserveCookieNames,
|
|
753
|
+
});
|
|
632
754
|
});
|
|
633
755
|
}
|
|
634
756
|
|
|
635
|
-
app.get("/healthz", (c) =>
|
|
636
|
-
|
|
757
|
+
app.get("/healthz", (c) => {
|
|
758
|
+
const warnings = githubAppBotIdentityWarnings(deps.settings);
|
|
759
|
+
return c.json({
|
|
637
760
|
service: deps.settings.serviceName,
|
|
638
761
|
environment: deps.settings.environment,
|
|
639
762
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
640
763
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
764
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
641
765
|
ok: true,
|
|
642
|
-
})
|
|
643
|
-
);
|
|
766
|
+
});
|
|
767
|
+
});
|
|
644
768
|
|
|
645
769
|
app.get("/readyz", async (c) => {
|
|
646
770
|
const result = await runReadinessChecks(readinessChecks(deps), 2_000);
|
|
@@ -661,10 +785,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
661
785
|
|
|
662
786
|
app.get("/v1/config/client", async (c) => {
|
|
663
787
|
c.header("cache-control", "no-store");
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
788
|
+
const resolvedCatalog = await resolveCatalogSettings(deps.db, deps.settings);
|
|
789
|
+
const baseCatalogSettings = resolvedCatalog.settings;
|
|
790
|
+
const codexCatalogSettings = baseCatalogSettings.codexSubscriptionEnabled
|
|
791
|
+
? withCodexCatalogProvider(baseCatalogSettings)
|
|
792
|
+
: baseCatalogSettings;
|
|
793
|
+
const catalogSettings = baseCatalogSettings.supergrokSubscriptionEnabled
|
|
668
794
|
? withXaiSubscriptionCatalogProvider(codexCatalogSettings)
|
|
669
795
|
: codexCatalogSettings;
|
|
670
796
|
return c.json(
|
|
@@ -681,6 +807,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
681
807
|
models: configuredModels(catalogSettings).map(projectClientModel),
|
|
682
808
|
defaultReasoningEffort: deps.settings.openaiReasoningEffort,
|
|
683
809
|
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
810
|
+
defaultSandboxBackend: deps.settings.sandboxBackend,
|
|
684
811
|
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
685
812
|
id: server.id,
|
|
686
813
|
name: server.name ?? server.id,
|
|
@@ -783,7 +910,8 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
783
910
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
784
911
|
enableJsonResponse: true,
|
|
785
912
|
});
|
|
786
|
-
const
|
|
913
|
+
const mcpDeps = await resolveWorkspaceMcpRouteDeps(routeDeps, grant);
|
|
914
|
+
const mcp = buildOpenGeniMcpServer(mcpDeps, grant, {
|
|
787
915
|
requestOrigin: new URL(c.req.url).origin,
|
|
788
916
|
workspaceMemoryEnabled,
|
|
789
917
|
workspaceMemoryPromptMode,
|
|
@@ -956,6 +1084,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
956
1084
|
return { app, routeDeps };
|
|
957
1085
|
}
|
|
958
1086
|
|
|
1087
|
+
function managedAuthOAuthCallbackProvider(pathname: string): "google" | "github" | null {
|
|
1088
|
+
const match = pathname.match(/^\/v1\/auth\/callback\/(google|github)$/u);
|
|
1089
|
+
return match?.[1] === "google" || match?.[1] === "github" ? match[1] : null;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
959
1092
|
function mutationOutcomeUnknown(error: unknown, method: string): boolean {
|
|
960
1093
|
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
961
1094
|
const cause = error instanceof HTTPException ? error.cause : error;
|
|
@@ -1072,6 +1205,14 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
1072
1205
|
mode: "managedSession" as const,
|
|
1073
1206
|
session: "cookie" as const,
|
|
1074
1207
|
emailVerificationRequired: settings.environment !== "local",
|
|
1208
|
+
socialProviders: [
|
|
1209
|
+
...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
|
|
1210
|
+
? (["google"] as const)
|
|
1211
|
+
: []),
|
|
1212
|
+
...(settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret
|
|
1213
|
+
? (["github"] as const)
|
|
1214
|
+
: []),
|
|
1215
|
+
],
|
|
1075
1216
|
};
|
|
1076
1217
|
}
|
|
1077
1218
|
if (settings.productAccessMode === "configured") {
|
|
@@ -1260,11 +1401,14 @@ type ReadinessCheckResult = { ok: boolean; error?: string };
|
|
|
1260
1401
|
function readinessChecks(deps: AppDependencies): ReadinessChecks {
|
|
1261
1402
|
const configuredNatsCheck = deps.readinessChecks?.nats;
|
|
1262
1403
|
return {
|
|
1263
|
-
db:
|
|
1264
|
-
deps.readinessChecks?.db
|
|
1265
|
-
|
|
1404
|
+
db: async () => {
|
|
1405
|
+
if (deps.readinessChecks?.db) {
|
|
1406
|
+
await deps.readinessChecks.db();
|
|
1407
|
+
} else {
|
|
1266
1408
|
await deps.db.execute(dbSql`select 1`);
|
|
1267
|
-
}
|
|
1409
|
+
}
|
|
1410
|
+
await resolveCatalogSettings(deps.db, deps.settings);
|
|
1411
|
+
},
|
|
1268
1412
|
nats: async () => {
|
|
1269
1413
|
requireSessionEventDurableFanoutCapability(deps.bus);
|
|
1270
1414
|
if (configuredNatsCheck) {
|
|
@@ -1354,6 +1498,22 @@ const routeLabelPatterns: Array<{
|
|
|
1354
1498
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
1355
1499
|
label: "/v1/workspaces/:workspaceId/codex/status",
|
|
1356
1500
|
},
|
|
1501
|
+
{
|
|
1502
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/source$/,
|
|
1503
|
+
label: "/v1/workspaces/:workspaceId/codex/source",
|
|
1504
|
+
},
|
|
1505
|
+
{
|
|
1506
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/(accounts|settings)$/,
|
|
1507
|
+
label: (match) => `/v1/organizations/:organizationId/codex/${match[1]}`,
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/connect\/(start|poll)$/,
|
|
1511
|
+
label: (match) => `/v1/organizations/:organizationId/codex/connect/${match[1]}`,
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/accounts\/[^/]+(?:\/activate)?$/,
|
|
1515
|
+
label: "/v1/organizations/:organizationId/codex/accounts/:accountId",
|
|
1516
|
+
},
|
|
1357
1517
|
{
|
|
1358
1518
|
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/connect\/(start|poll)$/,
|
|
1359
1519
|
label: (match) => `/v1/workspaces/:workspaceId/supergrok/connect/${match[1]}`,
|
|
@@ -1513,6 +1673,11 @@ const routeLabelPatterns: Array<{
|
|
|
1513
1673
|
label:
|
|
1514
1674
|
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/observation",
|
|
1515
1675
|
},
|
|
1676
|
+
{
|
|
1677
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/screenshot$/,
|
|
1678
|
+
label:
|
|
1679
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/screenshot",
|
|
1680
|
+
},
|
|
1516
1681
|
{
|
|
1517
1682
|
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
1518
1683
|
label:
|
|
@@ -1689,6 +1854,18 @@ const routeLabelPatterns: Array<{
|
|
|
1689
1854
|
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
|
|
1690
1855
|
label: "/v1/workspaces/:workspaceId/api-keys/:id",
|
|
1691
1856
|
},
|
|
1857
|
+
{
|
|
1858
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys$/,
|
|
1859
|
+
label: "/v1/organizations/:organizationId/api-keys",
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys\/[^/]+$/,
|
|
1863
|
+
label: "/v1/organizations/:organizationId/api-keys/:id",
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
pattern: /^\/v1\/workspaces\/external$/,
|
|
1867
|
+
label: "/v1/workspaces/external",
|
|
1868
|
+
},
|
|
1692
1869
|
{
|
|
1693
1870
|
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
1694
1871
|
label: "/v1/workspaces/:workspaceId/scheduled-tasks",
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
|
|
3
3
|
type ManagedAuthAttemptContext =
|
|
4
|
-
| {
|
|
4
|
+
| {
|
|
5
|
+
kind: "isolated_transaction";
|
|
6
|
+
transactionId: string;
|
|
7
|
+
providerId: "credential" | "google" | "github";
|
|
8
|
+
createdAuthSessionId: string | null;
|
|
9
|
+
}
|
|
10
|
+
| { kind: "provider"; providerId: "credential" | "google" | "github" }
|
|
5
11
|
| { kind: "discard_provider_session" };
|
|
6
12
|
|
|
7
13
|
const managedAuthAttemptStorage = new AsyncLocalStorage<ManagedAuthAttemptContext>();
|
|
8
14
|
|
|
9
|
-
export function runManagedAuthAttempt<T>(
|
|
10
|
-
|
|
15
|
+
export function runManagedAuthAttempt<T>(
|
|
16
|
+
transactionId: string,
|
|
17
|
+
providerId: "credential" | "google" | "github",
|
|
18
|
+
action: () => T,
|
|
19
|
+
): T {
|
|
20
|
+
return managedAuthAttemptStorage.run(
|
|
21
|
+
{ kind: "isolated_transaction", transactionId, providerId, createdAuthSessionId: null },
|
|
22
|
+
action,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function runManagedAuthProvider<T>(
|
|
27
|
+
providerId: "credential" | "google" | "github",
|
|
28
|
+
action: () => T,
|
|
29
|
+
): T {
|
|
30
|
+
return managedAuthAttemptStorage.run({ kind: "provider", providerId }, action);
|
|
11
31
|
}
|
|
12
32
|
|
|
13
33
|
export function runManagedAuthDiscardedProviderSession<T>(action: () => T): T {
|
|
@@ -19,6 +39,23 @@ export function currentManagedAuthAttemptId(): string | null {
|
|
|
19
39
|
return context?.kind === "isolated_transaction" ? context.transactionId : null;
|
|
20
40
|
}
|
|
21
41
|
|
|
42
|
+
export function currentManagedAuthProviderId(): "credential" | "google" | "github" {
|
|
43
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
44
|
+
return context?.kind === "isolated_transaction" || context?.kind === "provider"
|
|
45
|
+
? context.providerId
|
|
46
|
+
: "credential";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function recordCurrentManagedAuthSession(authSessionId: string): void {
|
|
50
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
51
|
+
if (context?.kind === "isolated_transaction") context.createdAuthSessionId = authSessionId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function currentManagedAuthCreatedSessionId(): string | null {
|
|
55
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
56
|
+
return context?.kind === "isolated_transaction" ? context.createdAuthSessionId : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
22
59
|
export function shouldDiscardCurrentManagedAuthProviderSession(): boolean {
|
|
23
60
|
return managedAuthAttemptStorage.getStore()?.kind === "discard_provider_session";
|
|
24
61
|
}
|
package/src/auth/managed-auth.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { canonicalPublicOrigin, type Settings } from "@opengeni/config";
|
|
2
2
|
import {
|
|
3
3
|
type ManagedAuth,
|
|
4
4
|
type ManagedEmailMessage,
|
|
@@ -20,7 +20,9 @@ import { Pool } from "pg";
|
|
|
20
20
|
|
|
21
21
|
import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-admission";
|
|
22
22
|
import {
|
|
23
|
+
currentManagedAuthProviderId,
|
|
23
24
|
currentManagedAuthAttemptId,
|
|
25
|
+
recordCurrentManagedAuthSession,
|
|
24
26
|
shouldDiscardCurrentManagedAuthProviderSession,
|
|
25
27
|
} from "./managed-auth-attempt-context";
|
|
26
28
|
|
|
@@ -44,6 +46,21 @@ export function managedAuthUserCreateOverride(
|
|
|
44
46
|
return { data: { ...user, emailVerified: true } };
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
export function managedAuthUserCreateAdmission(
|
|
50
|
+
settings: Pick<Settings, "environment">,
|
|
51
|
+
user: { emailVerified: boolean } & Record<string, unknown>,
|
|
52
|
+
providerId: string,
|
|
53
|
+
): { data: typeof user } | false | undefined {
|
|
54
|
+
if (
|
|
55
|
+
managedAuthRequiresEmailVerification(settings) &&
|
|
56
|
+
providerId !== "credential" &&
|
|
57
|
+
!user.emailVerified
|
|
58
|
+
) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return managedAuthUserCreateOverride(settings, user);
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
/** Keep Better Auth password policy and storage format behind this boundary. */
|
|
48
65
|
export async function hashManagedAuthPassword(password: string): Promise<string> {
|
|
49
66
|
return await hashPassword(password);
|
|
@@ -165,6 +182,27 @@ export function createManagedAuth(
|
|
|
165
182
|
accountLinking: {
|
|
166
183
|
enabled: false,
|
|
167
184
|
},
|
|
185
|
+
encryptOAuthTokens: true,
|
|
186
|
+
storeStateStrategy: "database",
|
|
187
|
+
},
|
|
188
|
+
socialProviders: {
|
|
189
|
+
...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
|
|
190
|
+
? {
|
|
191
|
+
google: {
|
|
192
|
+
clientId: settings.managedAuthGoogleClientId,
|
|
193
|
+
clientSecret: settings.managedAuthGoogleClientSecret,
|
|
194
|
+
prompt: "select_account" as const,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
: {}),
|
|
198
|
+
...(settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret
|
|
199
|
+
? {
|
|
200
|
+
github: {
|
|
201
|
+
clientId: settings.managedAuthGithubClientId,
|
|
202
|
+
clientSecret: settings.managedAuthGithubClientSecret,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
: {}),
|
|
168
206
|
},
|
|
169
207
|
verification: {
|
|
170
208
|
modelName: "auth_verifications",
|
|
@@ -229,6 +267,7 @@ export function createManagedAuth(
|
|
|
229
267
|
session: {
|
|
230
268
|
create: {
|
|
231
269
|
before: async (session) => {
|
|
270
|
+
const providerId = currentManagedAuthProviderId();
|
|
232
271
|
await ensureCanonicalHumanIdentityForAuthUser(db, session.userId);
|
|
233
272
|
const preflightProjection = await getCanonicalHumanIdentityProjection(
|
|
234
273
|
db,
|
|
@@ -242,7 +281,7 @@ export function createManagedAuth(
|
|
|
242
281
|
if (!preflight.allowed) {
|
|
243
282
|
const exactRecoveryBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
|
|
244
283
|
authUserId: session.userId,
|
|
245
|
-
providerId
|
|
284
|
+
providerId,
|
|
246
285
|
});
|
|
247
286
|
const recoveryBinding = preflightProjection.loginBindings.find(
|
|
248
287
|
(binding) =>
|
|
@@ -282,7 +321,7 @@ export function createManagedAuth(
|
|
|
282
321
|
const projection = await getCanonicalHumanIdentityProjection(db, session.userId);
|
|
283
322
|
const exactBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
|
|
284
323
|
authUserId: session.userId,
|
|
285
|
-
providerId
|
|
324
|
+
providerId,
|
|
286
325
|
});
|
|
287
326
|
const activeBinding = projection.loginBindings.find(
|
|
288
327
|
(binding) => binding.id === exactBinding.id,
|
|
@@ -318,6 +357,7 @@ export function createManagedAuth(
|
|
|
318
357
|
};
|
|
319
358
|
},
|
|
320
359
|
after: async (session) => {
|
|
360
|
+
recordCurrentManagedAuthSession(session.id);
|
|
321
361
|
if (!shouldDiscardCurrentManagedAuthProviderSession()) return;
|
|
322
362
|
await db.execute(sql`delete from auth_sessions where id = ${session.id}`);
|
|
323
363
|
},
|
|
@@ -325,7 +365,8 @@ export function createManagedAuth(
|
|
|
325
365
|
},
|
|
326
366
|
user: {
|
|
327
367
|
create: {
|
|
328
|
-
before: async (user) =>
|
|
368
|
+
before: async (user) =>
|
|
369
|
+
managedAuthUserCreateAdmission(settings, user, currentManagedAuthProviderId()),
|
|
329
370
|
after: async (user) => {
|
|
330
371
|
if (!user.emailVerified) return;
|
|
331
372
|
await ensureManagedAccessForUser(db, {
|
|
@@ -342,6 +383,125 @@ export function createManagedAuth(
|
|
|
342
383
|
}) as ManagedAuth;
|
|
343
384
|
}
|
|
344
385
|
|
|
386
|
+
export type ManagedAuthOAuthAttempt = {
|
|
387
|
+
transactionId: string;
|
|
388
|
+
provider: "google" | "github";
|
|
389
|
+
authorityHash: string;
|
|
390
|
+
transactionSecretHash: string;
|
|
391
|
+
expectedGeneration: string;
|
|
392
|
+
expectedActorEpoch: string;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Resolve OpenGeni's server-only login transaction proof from Better Auth's
|
|
397
|
+
* database-backed OAuth state before the provider callback consumes it.
|
|
398
|
+
*/
|
|
399
|
+
export async function resolveManagedAuthOAuthAttempt(
|
|
400
|
+
auth: ManagedAuth,
|
|
401
|
+
request: Request,
|
|
402
|
+
provider: "google" | "github",
|
|
403
|
+
publicBaseUrl: string,
|
|
404
|
+
): Promise<ManagedAuthOAuthAttempt | null> {
|
|
405
|
+
const expectedOrigin = canonicalPublicOrigin(publicBaseUrl);
|
|
406
|
+
if (!expectedOrigin) return null;
|
|
407
|
+
const state = new URL(request.url).searchParams.get("state");
|
|
408
|
+
if (!state) return null;
|
|
409
|
+
const verification = await (await auth.$context).internalAdapter.findVerificationValue(state);
|
|
410
|
+
if (!verification?.value) return null;
|
|
411
|
+
let parsed: unknown;
|
|
412
|
+
try {
|
|
413
|
+
parsed = JSON.parse(verification.value);
|
|
414
|
+
} catch {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
418
|
+
const stateData = parsed as Record<string, unknown>;
|
|
419
|
+
const proof = stateData.opengeniManagedAuth;
|
|
420
|
+
if (!proof || typeof proof !== "object") return null;
|
|
421
|
+
const value = proof as Record<string, unknown>;
|
|
422
|
+
if (
|
|
423
|
+
value.version !== 1 ||
|
|
424
|
+
value.provider !== provider ||
|
|
425
|
+
typeof value.transactionId !== "string" ||
|
|
426
|
+
typeof value.authorityHash !== "string" ||
|
|
427
|
+
typeof value.transactionSecretHash !== "string" ||
|
|
428
|
+
typeof value.expectedGeneration !== "string" ||
|
|
429
|
+
typeof value.expectedActorEpoch !== "string" ||
|
|
430
|
+
!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
|
|
431
|
+
value.transactionId,
|
|
432
|
+
) ||
|
|
433
|
+
!/^[0-9a-f]{64}$/u.test(value.authorityHash) ||
|
|
434
|
+
!/^[0-9a-f]{64}$/u.test(value.transactionSecretHash) ||
|
|
435
|
+
!/^[1-9][0-9]*$/u.test(value.expectedGeneration) ||
|
|
436
|
+
!/^[1-9][0-9]*$/u.test(value.expectedActorEpoch)
|
|
437
|
+
) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const callbackURL = typeof stateData.callbackURL === "string" ? stateData.callbackURL : null;
|
|
441
|
+
const errorURL = typeof stateData.errorURL === "string" ? stateData.errorURL : null;
|
|
442
|
+
if (
|
|
443
|
+
!callbackURL ||
|
|
444
|
+
!errorURL ||
|
|
445
|
+
!managedAuthOAuthReturnMatches(callbackURL, expectedOrigin, value.transactionId, "complete") ||
|
|
446
|
+
!managedAuthOAuthReturnMatches(errorURL, expectedOrigin, value.transactionId, "error")
|
|
447
|
+
) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
transactionId: value.transactionId,
|
|
452
|
+
provider,
|
|
453
|
+
authorityHash: value.authorityHash,
|
|
454
|
+
transactionSecretHash: value.transactionSecretHash,
|
|
455
|
+
expectedGeneration: value.expectedGeneration,
|
|
456
|
+
expectedActorEpoch: value.expectedActorEpoch,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export async function isolatedManagedAuthOAuthCallbackRequest(
|
|
461
|
+
auth: ManagedAuth,
|
|
462
|
+
request: Request,
|
|
463
|
+
): Promise<{ request: Request; stateCookieName: string }> {
|
|
464
|
+
const context = await auth.$context;
|
|
465
|
+
const stateCookieName = context.createAuthCookie("state").name;
|
|
466
|
+
const headers = new Headers(request.headers);
|
|
467
|
+
const stateCookie = cookiePair(headers.get("cookie"), stateCookieName);
|
|
468
|
+
if (stateCookie) headers.set("cookie", stateCookie);
|
|
469
|
+
else headers.delete("cookie");
|
|
470
|
+
headers.delete("authorization");
|
|
471
|
+
headers.delete("x-forwarded-user");
|
|
472
|
+
return { request: new Request(request, { headers }), stateCookieName };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function managedAuthOAuthReturnMatches(
|
|
476
|
+
raw: string,
|
|
477
|
+
expectedOrigin: string,
|
|
478
|
+
transactionId: string,
|
|
479
|
+
outcome: "complete" | "error",
|
|
480
|
+
): boolean {
|
|
481
|
+
try {
|
|
482
|
+
const url = new URL(raw);
|
|
483
|
+
return (
|
|
484
|
+
url.origin === expectedOrigin &&
|
|
485
|
+
url.pathname === "/account-auth" &&
|
|
486
|
+
url.searchParams.size === 2 &&
|
|
487
|
+
url.searchParams.get("transaction") === transactionId &&
|
|
488
|
+
url.searchParams.get("social") === outcome &&
|
|
489
|
+
!url.hash
|
|
490
|
+
);
|
|
491
|
+
} catch {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function cookiePair(header: string | null, name: string): string | null {
|
|
497
|
+
for (const part of header?.split(";") ?? []) {
|
|
498
|
+
const separator = part.indexOf("=");
|
|
499
|
+
if (separator < 0 || part.slice(0, separator).trim() !== name) continue;
|
|
500
|
+
return `${name}=${part.slice(separator + 1).trim()}`;
|
|
501
|
+
}
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
|
|
345
505
|
function betterAuthBaseUrl(settings: Settings) {
|
|
346
506
|
const allowedHosts = splitCsv(settings.betterAuthAllowedHosts);
|
|
347
507
|
if (allowedHosts.length === 0) {
|