@opengeni/api-router 3.0.1 → 4.0.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/admission-trace.d.ts +8 -0
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +2 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +2 -0
- package/dist/auth/managed-auth.d.ts +1 -0
- package/dist/auth/managed-sign-in-notifications.d.ts +4 -0
- package/dist/{chunk-AJ62T5QN.js → chunk-E7TZFJ65.js} +22300 -21113
- package/dist/{chunk-AJ62T5QN.js.map → chunk-E7TZFJ65.js.map} +1 -1
- package/dist/connection-ownership.d.ts +1 -2
- package/dist/fatal-process-boundary.d.ts +1 -1
- package/dist/index.js +24 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +8 -3
- package/dist/integrations/fiken.d.ts +3 -1
- package/dist/integrations/github-lens-connect.d.ts +5 -65
- package/dist/integrations/google-drive.d.ts +2 -0
- package/dist/integrations/integration-commit-authority.d.ts +13 -0
- package/dist/integrations/oauth-client.d.ts +119 -11
- package/dist/integrations/oauth-profiles.d.ts +8 -13
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/capability-account-readiness.d.ts +7 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +4 -0
- package/dist/mcp-oauth.d.ts +22 -1
- package/dist/routes/api-integrations.d.ts +1 -0
- package/dist/routes/codex.d.ts +28 -0
- package/dist/routes/managed-sign-in-methods.d.ts +6 -0
- package/dist/routes/organization-integration-policy.d.ts +3 -0
- package/dist/routes/sessions.d.ts +24 -2
- package/dist/scheduled-task-deletion.d.ts +3 -4
- package/dist/workspace-tool-gateway.d.ts +3 -1
- package/package.json +15 -15
- package/src/admission-trace.ts +14 -0
- package/src/app.ts +141 -94
- package/src/auth/managed-auth-attempt-context.ts +9 -0
- package/src/auth/managed-auth-session-adapter.ts +11 -0
- package/src/auth/managed-auth.ts +39 -2
- package/src/auth/managed-sign-in-notifications.ts +81 -0
- package/src/connection-ownership.ts +2 -9
- package/src/editable-artifact-production.ts +6 -0
- package/src/fatal-process-boundary.ts +17 -2
- package/src/index.ts +3 -4
- package/src/integrations/atlassian.ts +184 -76
- package/src/integrations/fiken.ts +57 -35
- package/src/integrations/github-app-connect.ts +51 -15
- package/src/integrations/github-lens-connect.ts +7 -28
- package/src/integrations/google-drive.ts +296 -125
- package/src/integrations/integration-commit-authority.ts +69 -0
- package/src/integrations/oauth-client.ts +231 -132
- package/src/integrations/oauth-profiles.ts +10 -45
- package/src/integrations/personal-github.ts +55 -31
- package/src/integrations/provider-oauth.ts +57 -82
- package/src/integrations/social-oauth.ts +83 -56
- package/src/mcp/capability-account-readiness.ts +17 -0
- package/src/mcp/editable-artifacts.ts +7 -2
- package/src/mcp/first-party-tool-permissions.ts +1 -0
- package/src/mcp/knowledge-entries.ts +25 -8
- package/src/mcp/server.ts +135 -43
- package/src/mcp-oauth.ts +336 -34
- package/src/routes/api-integrations.ts +116 -4
- package/src/routes/automations.ts +5 -13
- package/src/routes/billing.ts +52 -0
- package/src/routes/canonical-human-identities.ts +61 -0
- package/src/routes/capabilities.ts +51 -1
- package/src/routes/codex.ts +4 -2
- package/src/routes/connect.ts +90 -31
- package/src/routes/connections.ts +487 -307
- package/src/routes/files.ts +21 -12
- package/src/routes/github.ts +48 -19
- package/src/routes/insights.ts +17 -7
- package/src/routes/integration-facets.ts +66 -25
- package/src/routes/knowledge.ts +48 -1
- package/src/routes/managed-sign-in-methods.ts +433 -0
- package/src/routes/organization-integration-policy.ts +113 -0
- package/src/routes/organization-memberships.ts +22 -2
- package/src/routes/plugins.ts +42 -4
- package/src/routes/pr-review-github.ts +67 -57
- package/src/routes/pr-review.ts +74 -71
- package/src/routes/rigs.ts +32 -13
- package/src/routes/scheduled-tasks.ts +6 -6
- package/src/routes/sessions.ts +233 -11
- package/src/routes/skill-content.ts +19 -3
- package/src/routes/social.ts +24 -13
- package/src/sandbox/channel-a.ts +7 -1
- package/src/sandbox/rematerialize.ts +2 -1
- package/src/scheduled-task-deletion.ts +13 -3
- package/src/workspace-tool-gateway.ts +31 -31
- package/dist/connection-authority-owner.d.ts +0 -59
- package/dist/routes/connection-authorities.d.ts +0 -3
- package/dist/routes/host-mcp-bindings.d.ts +0 -5
- package/dist/routes/host-mcp-resolvers.d.ts +0 -3
- package/dist/routes/packs.d.ts +0 -3
- package/src/connection-authority-owner.ts +0 -61
- package/src/routes/connection-authorities.ts +0 -139
- package/src/routes/host-mcp-bindings.ts +0 -122
- package/src/routes/host-mcp-resolvers.ts +0 -41
- package/src/routes/packs.ts +0 -784
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -44,22 +44,22 @@
|
|
|
44
44
|
"@llamaindex/liteparse": "2.14.2",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
46
|
"@opengeni/agent-proto": "^0.6.0",
|
|
47
|
-
"@opengeni/artifact-tool": "^0.3.
|
|
47
|
+
"@opengeni/artifact-tool": "^0.3.30",
|
|
48
48
|
"@opengeni/capabilities": "^0.3.4",
|
|
49
|
-
"@opengeni/codemode": "^0.
|
|
50
|
-
"@opengeni/codex": "^0.2.
|
|
51
|
-
"@opengeni/config": "^
|
|
52
|
-
"@opengeni/contracts": "^
|
|
53
|
-
"@opengeni/core": "^
|
|
54
|
-
"@opengeni/db": "^
|
|
55
|
-
"@opengeni/documents": "^0.8.
|
|
56
|
-
"@opengeni/events": "^0.4.
|
|
57
|
-
"@opengeni/github": "^0.7.
|
|
49
|
+
"@opengeni/codemode": "^0.6.0",
|
|
50
|
+
"@opengeni/codex": "^0.2.24",
|
|
51
|
+
"@opengeni/config": "^2.0.0",
|
|
52
|
+
"@opengeni/contracts": "^5.0.0",
|
|
53
|
+
"@opengeni/core": "^4.0.0",
|
|
54
|
+
"@opengeni/db": "^6.0.0",
|
|
55
|
+
"@opengeni/documents": "^0.8.31",
|
|
56
|
+
"@opengeni/events": "^0.4.29",
|
|
57
|
+
"@opengeni/github": "^0.7.14",
|
|
58
58
|
"@opengeni/network": "^0.3.1",
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
62
|
-
"@opengeni/tool-gateway": "^0.1.
|
|
59
|
+
"@opengeni/observability": "^0.8.30",
|
|
60
|
+
"@opengeni/runtime": "^4.0.0",
|
|
61
|
+
"@opengeni/storage": "^0.2.131",
|
|
62
|
+
"@opengeni/tool-gateway": "^0.1.11",
|
|
63
63
|
"@opengeni/xai-subscription": "^0.1.4",
|
|
64
64
|
"@temporalio/client": "^1.17.0",
|
|
65
65
|
"better-auth": "^1.6.14",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Observability } from "@opengeni/observability";
|
|
2
|
+
|
|
3
|
+
/** Only committed, non-replayed server event identities mint admission anchors. */
|
|
4
|
+
export function recordAcceptedApiAdmission(
|
|
5
|
+
observability: Observability | undefined,
|
|
6
|
+
result: { accepted: { id: string }; replay: boolean },
|
|
7
|
+
): void {
|
|
8
|
+
if (result.replay) return;
|
|
9
|
+
try {
|
|
10
|
+
observability?.recordAdmissionTrace(result.accepted.id);
|
|
11
|
+
} catch {
|
|
12
|
+
// Telemetry must never turn a committed admission into an HTTP failure.
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/app.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerConnectCallbackReturns } from "./integrations/connect-callback-return";
|
|
2
2
|
import { registerFeedbackRoutes } from "./routes/feedback";
|
|
3
3
|
import { codemodeSessionRequest } from "./codemode";
|
|
4
|
-
import { SiteSessionPathError } from "@opengeni/contracts";
|
|
4
|
+
import { SiteSessionPathError, OrganizationIntegrationDeniedError } from "@opengeni/contracts";
|
|
5
5
|
import { registerModelConnectionAccessRoutes } from "./routes/model-connection-access";
|
|
6
6
|
import {
|
|
7
7
|
canonicalizeConfiguredModelId,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
} from "@opengeni/db";
|
|
56
56
|
import { requireSessionEventDurableFanoutCapability } from "@opengeni/events";
|
|
57
57
|
import { githubAppBotIdentityWarnings } from "@opengeni/github";
|
|
58
|
-
import { createObservability } from "@opengeni/observability";
|
|
58
|
+
import { createObservability, withTraceContext } from "@opengeni/observability";
|
|
59
59
|
import { createObjectStorage } from "@opengeni/storage";
|
|
60
60
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
61
61
|
import { handleMcpRequestWithClientAbort } from "./mcp/request-abort";
|
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
runManagedAuthProvider,
|
|
107
107
|
} from "./auth/managed-auth-attempt-context";
|
|
108
108
|
import { createManagedEmailTransport } from "./auth/managed-email";
|
|
109
|
+
import { startManagedSignInNotificationDelivery } from "./auth/managed-sign-in-notifications";
|
|
109
110
|
import {
|
|
110
111
|
assertManagedEmailTransportMetadata,
|
|
111
112
|
assertOrganizationUserSetupQueryTransportConfigured,
|
|
@@ -149,11 +150,11 @@ import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
|
149
150
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
150
151
|
import { registerCodexRoutes } from "./routes/codex";
|
|
151
152
|
import { registerOrganizationModelProviderRoutes } from "./routes/organization-model-providers";
|
|
153
|
+
import { registerOrganizationIntegrationPolicyRoutes } from "./routes/organization-integration-policy";
|
|
152
154
|
import { registerSuperGrokRoutes } from "./routes/supergrok";
|
|
153
155
|
import { registerConnectionRoutes } from "./routes/connections";
|
|
154
156
|
import { registerConnectRoutes } from "./routes/connect";
|
|
155
|
-
|
|
156
|
-
import { registerHostMcpResolverRoutes } from "./routes/host-mcp-resolvers";
|
|
157
|
+
|
|
157
158
|
import { registerExternalIdentityLinkRoutes } from "./routes/external-identity-links";
|
|
158
159
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
159
160
|
import { registerKnowledgeRoutes } from "./routes/knowledge";
|
|
@@ -178,7 +179,6 @@ import { registerApiIntegrationRoutes } from "./routes/api-integrations";
|
|
|
178
179
|
import { registerIntegrationFacetRoutes } from "./routes/integration-facets";
|
|
179
180
|
import { registerInteractionResourceRoutes } from "./routes/interaction-resources";
|
|
180
181
|
import { registerPrReviewRoutes } from "./routes/pr-review";
|
|
181
|
-
import { registerPackRoutes } from "./routes/packs";
|
|
182
182
|
import { registerAutomationRoutes } from "./routes/automations";
|
|
183
183
|
import { registerPluginRoutes } from "./routes/plugins";
|
|
184
184
|
import { registerSkillRoutes } from "./routes/skills";
|
|
@@ -206,13 +206,16 @@ import { registerOrganizationMembershipRoutes } from "./routes/organization-memb
|
|
|
206
206
|
import { registerOrganizationSessionRoutes } from "./routes/organization-sessions";
|
|
207
207
|
import { registerOrganizationRecoveryRoutes } from "./routes/organization-recovery";
|
|
208
208
|
import { registerManagedOnboardingRoutes } from "./routes/managed-onboarding";
|
|
209
|
+
import {
|
|
210
|
+
registerManagedSignInMethodRoutes,
|
|
211
|
+
handleManagedSignInConnectCallback,
|
|
212
|
+
} from "./routes/managed-sign-in-methods";
|
|
209
213
|
import {
|
|
210
214
|
registerManagedAuthSessionSetRoutes,
|
|
211
215
|
requireManagedAuthProviderRouteAllowed,
|
|
212
216
|
scrubManagedAuthProviderResponse,
|
|
213
217
|
} from "./routes/managed-auth-session-sets";
|
|
214
218
|
import { registerUserResourceAuthorityRoutes } from "./routes/user-resource-authorities";
|
|
215
|
-
import { registerConnectionAuthorityRoutes } from "./routes/connection-authorities";
|
|
216
219
|
import { projectClientModel } from "./model-catalog";
|
|
217
220
|
import { createTranscriptionService } from "./transcription/service";
|
|
218
221
|
import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
|
|
@@ -299,6 +302,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
299
302
|
const managedAuthSessionAdapter =
|
|
300
303
|
deps.managedAuthSessionAdapter ??
|
|
301
304
|
(managedAuth ? createBetterAuthSessionAdapter(managedAuth, deps.db) : null);
|
|
305
|
+
if (managedAuth) startManagedSignInNotificationDelivery(deps.db, managedEmailTransport);
|
|
302
306
|
const objectStorage =
|
|
303
307
|
deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
|
|
304
308
|
let documentServices: DocumentServices | null = deps.documentServices ?? null;
|
|
@@ -538,70 +542,76 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
538
542
|
const route = routeLabel(url.pathname);
|
|
539
543
|
const correlationId = correlationIds.get(c.req.raw) ?? crypto.randomUUID();
|
|
540
544
|
const start = performance.now();
|
|
541
|
-
const span = observability.startSpan(
|
|
542
|
-
|
|
543
|
-
|
|
545
|
+
const span = observability.startSpan(
|
|
546
|
+
`HTTP ${c.req.method} ${route}`,
|
|
547
|
+
{
|
|
548
|
+
"http.request.method": c.req.method,
|
|
549
|
+
"opengeni.route": route,
|
|
550
|
+
},
|
|
551
|
+
{ parent: null },
|
|
552
|
+
);
|
|
553
|
+
return await withTraceContext(span, async () => {
|
|
554
|
+
try {
|
|
555
|
+
await next();
|
|
556
|
+
const status = c.res.status || 200;
|
|
557
|
+
const durationSeconds = (performance.now() - start) / 1000;
|
|
558
|
+
observability.recordHttpRequest({
|
|
559
|
+
method: c.req.method,
|
|
560
|
+
route,
|
|
561
|
+
status,
|
|
562
|
+
durationSeconds,
|
|
563
|
+
});
|
|
564
|
+
span.end({
|
|
565
|
+
attributes: {
|
|
566
|
+
"http.response.status_code": status,
|
|
567
|
+
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
observability.info("HTTP request completed", {
|
|
571
|
+
method: c.req.method,
|
|
572
|
+
route,
|
|
573
|
+
status,
|
|
574
|
+
durationMs: Math.round(durationSeconds * 1000),
|
|
575
|
+
traceId: span.traceId,
|
|
576
|
+
spanId: span.spanId,
|
|
577
|
+
correlationId,
|
|
578
|
+
});
|
|
579
|
+
} catch (error) {
|
|
580
|
+
const status = httpStatusForError(error);
|
|
581
|
+
const errorCode = errorCodeForStatus(status);
|
|
582
|
+
const durationSeconds = (performance.now() - start) / 1000;
|
|
583
|
+
observability.recordHttpRequest({
|
|
584
|
+
method: c.req.method,
|
|
585
|
+
route,
|
|
586
|
+
status,
|
|
587
|
+
durationSeconds,
|
|
588
|
+
});
|
|
589
|
+
observability.incrementCounter({
|
|
590
|
+
name: "opengeni_http_errors_total",
|
|
591
|
+
help: "Total OpenGeni HTTP request failures by bounded route, status, and stable code.",
|
|
592
|
+
labels: { route, status: String(status), code: errorCode },
|
|
593
|
+
});
|
|
594
|
+
span.end({
|
|
595
|
+
attributes: {
|
|
596
|
+
"http.response.status_code": status,
|
|
597
|
+
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
598
|
+
},
|
|
599
|
+
error,
|
|
600
|
+
});
|
|
601
|
+
observability.error("HTTP request failed", {
|
|
602
|
+
method: c.req.method,
|
|
603
|
+
route,
|
|
604
|
+
status,
|
|
605
|
+
durationMs: Math.round(durationSeconds * 1000),
|
|
606
|
+
traceId: span.traceId,
|
|
607
|
+
spanId: span.spanId,
|
|
608
|
+
correlationId,
|
|
609
|
+
errorCode,
|
|
610
|
+
errorClass: "HttpOperationError",
|
|
611
|
+
});
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
544
614
|
});
|
|
545
|
-
try {
|
|
546
|
-
await next();
|
|
547
|
-
const status = c.res.status || 200;
|
|
548
|
-
const durationSeconds = (performance.now() - start) / 1000;
|
|
549
|
-
observability.recordHttpRequest({
|
|
550
|
-
method: c.req.method,
|
|
551
|
-
route,
|
|
552
|
-
status,
|
|
553
|
-
durationSeconds,
|
|
554
|
-
});
|
|
555
|
-
span.end({
|
|
556
|
-
attributes: {
|
|
557
|
-
"http.response.status_code": status,
|
|
558
|
-
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
observability.info("HTTP request completed", {
|
|
562
|
-
method: c.req.method,
|
|
563
|
-
route,
|
|
564
|
-
status,
|
|
565
|
-
durationMs: Math.round(durationSeconds * 1000),
|
|
566
|
-
traceId: span.traceId,
|
|
567
|
-
spanId: span.spanId,
|
|
568
|
-
correlationId,
|
|
569
|
-
});
|
|
570
|
-
} catch (error) {
|
|
571
|
-
const status = httpStatusForError(error);
|
|
572
|
-
const errorCode = errorCodeForStatus(status);
|
|
573
|
-
const durationSeconds = (performance.now() - start) / 1000;
|
|
574
|
-
observability.recordHttpRequest({
|
|
575
|
-
method: c.req.method,
|
|
576
|
-
route,
|
|
577
|
-
status,
|
|
578
|
-
durationSeconds,
|
|
579
|
-
});
|
|
580
|
-
observability.incrementCounter({
|
|
581
|
-
name: "opengeni_http_errors_total",
|
|
582
|
-
help: "Total OpenGeni HTTP request failures by bounded route, status, and stable code.",
|
|
583
|
-
labels: { route, status: String(status), code: errorCode },
|
|
584
|
-
});
|
|
585
|
-
span.end({
|
|
586
|
-
attributes: {
|
|
587
|
-
"http.response.status_code": status,
|
|
588
|
-
"opengeni.duration_ms": Math.round(durationSeconds * 1000),
|
|
589
|
-
},
|
|
590
|
-
error,
|
|
591
|
-
});
|
|
592
|
-
observability.error("HTTP request failed", {
|
|
593
|
-
method: c.req.method,
|
|
594
|
-
route,
|
|
595
|
-
status,
|
|
596
|
-
durationMs: Math.round(durationSeconds * 1000),
|
|
597
|
-
traceId: span.traceId,
|
|
598
|
-
spanId: span.spanId,
|
|
599
|
-
correlationId,
|
|
600
|
-
errorCode,
|
|
601
|
-
errorClass: "HttpOperationError",
|
|
602
|
-
});
|
|
603
|
-
throw error;
|
|
604
|
-
}
|
|
605
615
|
});
|
|
606
616
|
|
|
607
617
|
const accessKeyBoundary = requireAccessKey(deps.settings);
|
|
@@ -669,10 +679,61 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
669
679
|
// wildcard handler or the provider returns its own 404 first.
|
|
670
680
|
registerManagedOnboardingRoutes(app, routeDeps);
|
|
671
681
|
registerManagedAuthSessionSetRoutes(app, routeDeps);
|
|
682
|
+
registerManagedSignInMethodRoutes(app, routeDeps);
|
|
672
683
|
if (managedAuth) {
|
|
673
684
|
app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
|
|
674
685
|
const pathname = new URL(c.req.url).pathname;
|
|
675
686
|
const oauthCallbackProvider = managedAuthOAuthCallbackProvider(pathname);
|
|
687
|
+
if (pathname === "/v1/auth/sign-in/social" && c.req.method === "POST") {
|
|
688
|
+
const body = await c.req.raw
|
|
689
|
+
.clone()
|
|
690
|
+
.json()
|
|
691
|
+
.catch(() => null);
|
|
692
|
+
if (
|
|
693
|
+
!body ||
|
|
694
|
+
!["google", "github"].includes(body.provider) ||
|
|
695
|
+
Object.prototype.hasOwnProperty.call(body, "idToken")
|
|
696
|
+
) {
|
|
697
|
+
return c.json(
|
|
698
|
+
{
|
|
699
|
+
code: "SIGN_IN_METHOD_OAUTH_REDIRECT_REQUIRED",
|
|
700
|
+
message: "Use the browser OAuth sign-in redirect",
|
|
701
|
+
},
|
|
702
|
+
403,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (oauthCallbackProvider) {
|
|
707
|
+
const connectResponse = await handleManagedSignInConnectCallback(
|
|
708
|
+
c,
|
|
709
|
+
routeDeps,
|
|
710
|
+
oauthCallbackProvider,
|
|
711
|
+
);
|
|
712
|
+
if (connectResponse) return connectResponse;
|
|
713
|
+
}
|
|
714
|
+
if (
|
|
715
|
+
new Set([
|
|
716
|
+
"link-social",
|
|
717
|
+
"unlink-account",
|
|
718
|
+
"list-accounts",
|
|
719
|
+
"set-password",
|
|
720
|
+
"change-password",
|
|
721
|
+
"change-email",
|
|
722
|
+
"update-user",
|
|
723
|
+
"delete-user",
|
|
724
|
+
"get-access-token",
|
|
725
|
+
"refresh-token",
|
|
726
|
+
"account-info",
|
|
727
|
+
]).has(pathname.slice("/v1/auth/".length))
|
|
728
|
+
) {
|
|
729
|
+
return c.json(
|
|
730
|
+
{
|
|
731
|
+
code: "SIGN_IN_METHOD_PRODUCT_ROUTE_REQUIRED",
|
|
732
|
+
message: "Use personal sign-in method settings",
|
|
733
|
+
},
|
|
734
|
+
403,
|
|
735
|
+
);
|
|
736
|
+
}
|
|
676
737
|
if (deps.settings.managedAuthSessionSetMode === "legacy") {
|
|
677
738
|
return oauthCallbackProvider
|
|
678
739
|
? await runManagedAuthProvider(
|
|
@@ -1256,8 +1317,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1256
1317
|
registerPersonalGitHubGitBrokerRoutes(app, routeDeps);
|
|
1257
1318
|
registerConnectionRoutes(app, routeDeps);
|
|
1258
1319
|
registerConnectRoutes(app, routeDeps);
|
|
1259
|
-
|
|
1260
|
-
registerHostMcpResolverRoutes(app, routeDeps);
|
|
1320
|
+
|
|
1261
1321
|
registerExternalIdentityLinkRoutes(app, routeDeps);
|
|
1262
1322
|
registerCapabilityRoutes(app, routeDeps);
|
|
1263
1323
|
registerApiIntegrationRoutes(app, routeDeps);
|
|
@@ -1268,7 +1328,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1268
1328
|
registerEnvironmentRoutes(app, routeDeps);
|
|
1269
1329
|
registerChannelRoutes(app, routeDeps);
|
|
1270
1330
|
registerRigRoutes(app, routeDeps);
|
|
1271
|
-
registerPackRoutes(app, routeDeps);
|
|
1272
1331
|
registerAutomationRoutes(app, routeDeps);
|
|
1273
1332
|
registerPrReviewRoutes(app, routeDeps);
|
|
1274
1333
|
registerPluginRoutes(app, routeDeps);
|
|
@@ -1278,6 +1337,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1278
1337
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
1279
1338
|
registerCodexRoutes(app, routeDeps);
|
|
1280
1339
|
registerOrganizationModelProviderRoutes(app, routeDeps);
|
|
1340
|
+
registerOrganizationIntegrationPolicyRoutes(app, routeDeps);
|
|
1281
1341
|
registerModelConnectionAccessRoutes(app, routeDeps);
|
|
1282
1342
|
registerSuperGrokRoutes(app, routeDeps);
|
|
1283
1343
|
registerTranscriptionRoutes(app, routeDeps);
|
|
@@ -1288,7 +1348,6 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1288
1348
|
registerOrganizationSessionRoutes(app, routeDeps);
|
|
1289
1349
|
registerOrganizationRecoveryRoutes(app, routeDeps);
|
|
1290
1350
|
registerUserResourceAuthorityRoutes(app, routeDeps);
|
|
1291
|
-
registerConnectionAuthorityRoutes(app, routeDeps);
|
|
1292
1351
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
1293
1352
|
|
|
1294
1353
|
app.notFound((c) => {
|
|
@@ -1311,7 +1370,10 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
1311
1370
|
app.onError((rawError, c) => {
|
|
1312
1371
|
// One central mapping for every Send/Steer/control route: a bounded
|
|
1313
1372
|
// control-prefix wait that expired is a known, retryable, not-applied 503.
|
|
1314
|
-
const error =
|
|
1373
|
+
const error =
|
|
1374
|
+
rawError instanceof OrganizationIntegrationDeniedError
|
|
1375
|
+
? new HTTPException(403, { message: rawError.message })
|
|
1376
|
+
: (workspaceControlBusyHttpError(rawError) ?? rawError);
|
|
1315
1377
|
const compactionLock = codexCompactionV2ProviderLockedError(error);
|
|
1316
1378
|
const apiError = error instanceof ApiHttpError ? error : null;
|
|
1317
1379
|
const status = compactionLock ? 422 : httpStatusForError(error);
|
|
@@ -1861,6 +1923,10 @@ const routeLabelPatterns: Array<{
|
|
|
1861
1923
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/,
|
|
1862
1924
|
label: "/v1/workspaces/:workspaceId/sessions",
|
|
1863
1925
|
},
|
|
1926
|
+
{
|
|
1927
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/session-message-search$/,
|
|
1928
|
+
label: "/v1/workspaces/:workspaceId/session-message-search",
|
|
1929
|
+
},
|
|
1864
1930
|
{
|
|
1865
1931
|
pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
|
|
1866
1932
|
label: "/v1/workspaces/:workspaceId/control-events/stream",
|
|
@@ -2373,26 +2439,7 @@ const routeLabelPatterns: Array<{
|
|
|
2373
2439
|
pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+$/,
|
|
2374
2440
|
label: "/v1/workspaces/:workspaceId/environments/:id",
|
|
2375
2441
|
},
|
|
2376
|
-
|
|
2377
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs$/,
|
|
2378
|
-
label: "/v1/workspaces/:workspaceId/packs",
|
|
2379
|
-
},
|
|
2380
|
-
{
|
|
2381
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/installations$/,
|
|
2382
|
-
label: "/v1/workspaces/:workspaceId/packs/installations",
|
|
2383
|
-
},
|
|
2384
|
-
{
|
|
2385
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/marketing-social-daily-analysis\/scheduled-tasks$/,
|
|
2386
|
-
label: "/v1/workspaces/:workspaceId/packs/marketing-social-daily-analysis/scheduled-tasks",
|
|
2387
|
-
},
|
|
2388
|
-
{
|
|
2389
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+\/enable$/,
|
|
2390
|
-
label: "/v1/workspaces/:workspaceId/packs/:id/enable",
|
|
2391
|
-
},
|
|
2392
|
-
{
|
|
2393
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/packs\/[^/]+$/,
|
|
2394
|
-
label: "/v1/workspaces/:workspaceId/packs/:id",
|
|
2395
|
-
},
|
|
2442
|
+
|
|
2396
2443
|
{
|
|
2397
2444
|
pattern: /^\/v1\/workspaces\/[^/]+\/plugins\/preview$/,
|
|
2398
2445
|
label: "/v1/workspaces/:workspaceId/plugins/preview",
|
|
@@ -11,6 +11,15 @@ type ManagedAuthAttemptContext =
|
|
|
11
11
|
| { kind: "discard_provider_session" };
|
|
12
12
|
|
|
13
13
|
const managedAuthAttemptStorage = new AsyncLocalStorage<ManagedAuthAttemptContext>();
|
|
14
|
+
const managedLinkIntentStorage = new AsyncLocalStorage<string>();
|
|
15
|
+
|
|
16
|
+
export function runManagedSignInConnect<T>(intentId: string, action: () => T): T {
|
|
17
|
+
return managedLinkIntentStorage.run(intentId, action);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function currentManagedSignInConnectIntent(): string | undefined {
|
|
21
|
+
return managedLinkIntentStorage.getStore();
|
|
22
|
+
}
|
|
14
23
|
|
|
15
24
|
export function runManagedAuthAttempt<T>(
|
|
16
25
|
transactionId: string,
|
|
@@ -8,6 +8,17 @@ import type { ManagedAuth } from "@opengeni/core";
|
|
|
8
8
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
9
9
|
import { runManagedAuthAttempt } from "./managed-auth-attempt-context";
|
|
10
10
|
|
|
11
|
+
/** Server-only proof for a product-owned endpoint; never returned to a browser. */
|
|
12
|
+
export async function managedAuthSelectedProofHeaders(
|
|
13
|
+
auth: ManagedAuth,
|
|
14
|
+
token: string,
|
|
15
|
+
): Promise<Headers> {
|
|
16
|
+
const context = await auth.$context;
|
|
17
|
+
return new Headers({
|
|
18
|
+
cookie: `${context.authCookies.sessionToken.name}=${signedCookieValue(token, context.secret)}`,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
export function createBetterAuthSessionAdapter(
|
|
12
23
|
auth: ManagedAuth,
|
|
13
24
|
db: Database,
|
package/src/auth/managed-auth.ts
CHANGED
|
@@ -14,16 +14,18 @@ import {
|
|
|
14
14
|
} from "@opengeni/db/canonical-human-identities";
|
|
15
15
|
import { betterAuth } from "better-auth";
|
|
16
16
|
import { createEmailVerificationToken } from "better-auth/api";
|
|
17
|
-
import { hashPassword } from "better-auth/crypto";
|
|
17
|
+
import { hashPassword, verifyPassword } from "better-auth/crypto";
|
|
18
18
|
import { sql } from "drizzle-orm";
|
|
19
19
|
import { Pool } from "pg";
|
|
20
20
|
|
|
21
21
|
import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-admission";
|
|
22
|
+
import { deliverManagedSignInNotification } from "./managed-sign-in-notifications";
|
|
22
23
|
import {
|
|
23
24
|
currentManagedAuthProviderId,
|
|
24
25
|
currentManagedAuthAttemptId,
|
|
25
26
|
recordCurrentManagedAuthSession,
|
|
26
27
|
shouldDiscardCurrentManagedAuthProviderSession,
|
|
28
|
+
currentManagedSignInConnectIntent,
|
|
27
29
|
} from "./managed-auth-attempt-context";
|
|
28
30
|
|
|
29
31
|
// `ManagedAuth` (the Better Auth `Auth<any>` alias) is owned by @opengeni/core
|
|
@@ -66,6 +68,10 @@ export async function hashManagedAuthPassword(password: string): Promise<string>
|
|
|
66
68
|
return await hashPassword(password);
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
export async function verifyManagedAuthPassword(password: string, hash: string): Promise<boolean> {
|
|
72
|
+
return await verifyPassword({ password, hash });
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
export function createManagedAuth(
|
|
70
76
|
settings: Settings,
|
|
71
77
|
db: Database,
|
|
@@ -180,7 +186,18 @@ export function createManagedAuth(
|
|
|
180
186
|
updatedAt: "updated_at",
|
|
181
187
|
},
|
|
182
188
|
accountLinking: {
|
|
183
|
-
enabled:
|
|
189
|
+
enabled: true,
|
|
190
|
+
requireLocalEmailVerified: true,
|
|
191
|
+
trustedProviders: [],
|
|
192
|
+
allowDifferentEmails: false,
|
|
193
|
+
},
|
|
194
|
+
additionalFields: {
|
|
195
|
+
managedLinkIntentId: {
|
|
196
|
+
type: "string",
|
|
197
|
+
fieldName: "managed_link_intent_id",
|
|
198
|
+
input: false,
|
|
199
|
+
returned: false,
|
|
200
|
+
},
|
|
184
201
|
},
|
|
185
202
|
encryptOAuthTokens: true,
|
|
186
203
|
storeStateStrategy: "database",
|
|
@@ -264,6 +281,26 @@ export function createManagedAuth(
|
|
|
264
281
|
},
|
|
265
282
|
},
|
|
266
283
|
databaseHooks: {
|
|
284
|
+
account: {
|
|
285
|
+
create: {
|
|
286
|
+
before: async (account) => ({
|
|
287
|
+
data: {
|
|
288
|
+
...account,
|
|
289
|
+
...(currentManagedSignInConnectIntent()
|
|
290
|
+
? { managedLinkIntentId: currentManagedSignInConnectIntent() }
|
|
291
|
+
: {}),
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
after: async (account) => {
|
|
295
|
+
if (
|
|
296
|
+
!["google", "github"].includes(account.providerId) ||
|
|
297
|
+
currentManagedSignInConnectIntent()
|
|
298
|
+
)
|
|
299
|
+
return;
|
|
300
|
+
await deliverManagedSignInNotification(db, managedEmailTransport, account.id);
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
267
304
|
session: {
|
|
268
305
|
create: {
|
|
269
306
|
before: async (session) => {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ManagedEmailTransport } from "@opengeni/core";
|
|
2
|
+
import type { Database } from "@opengeni/db";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const Claim = z.object({
|
|
7
|
+
operationId: z.string().uuid(),
|
|
8
|
+
leaseId: z.string().uuid(),
|
|
9
|
+
email: z.string().email(),
|
|
10
|
+
provider: z.enum(["password", "google", "github"]),
|
|
11
|
+
sender: z.string(),
|
|
12
|
+
});
|
|
13
|
+
const started = new WeakSet<object>();
|
|
14
|
+
|
|
15
|
+
export async function deliverManagedSignInNotification(
|
|
16
|
+
db: Database,
|
|
17
|
+
transport: ManagedEmailTransport,
|
|
18
|
+
operationId: string | null = null,
|
|
19
|
+
): Promise<"sent" | "failed" | "outcome_unknown"> {
|
|
20
|
+
try {
|
|
21
|
+
const result = await db.execute(
|
|
22
|
+
sql`select claim_managed_sign_in_notification(${operationId}::uuid,${transport.sender},${transport.idempotency.scope},${transport.idempotency.retentionSeconds}::integer) as claim`,
|
|
23
|
+
);
|
|
24
|
+
const row = (Array.isArray(result) ? result : result.rows)[0] as { claim: unknown } | undefined;
|
|
25
|
+
if (!row?.claim) return "outcome_unknown";
|
|
26
|
+
const claim = Claim.parse(row.claim);
|
|
27
|
+
let status: "sent" | "failed" | "outcome_unknown" = "outcome_unknown";
|
|
28
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
29
|
+
try {
|
|
30
|
+
const response = await Promise.race([
|
|
31
|
+
transport.send({
|
|
32
|
+
kind: "sign_in_method_changed",
|
|
33
|
+
from: claim.sender,
|
|
34
|
+
to: claim.email,
|
|
35
|
+
idempotencyKey: `sign-in-method:${claim.operationId}`,
|
|
36
|
+
subject: "Your OpenGeni sign-in methods changed",
|
|
37
|
+
text: `Your OpenGeni ${claim.provider} sign-in method changed. If this was not you, reset your password and contact your administrator.`,
|
|
38
|
+
html: `<p>Your OpenGeni ${claim.provider} sign-in method changed. If this was not you, reset your password and contact your administrator.</p>`,
|
|
39
|
+
}),
|
|
40
|
+
new Promise<{ status: "outcome_unknown" }>((resolve) => {
|
|
41
|
+
timer = setTimeout(() => resolve({ status: "outcome_unknown" }), 5_000);
|
|
42
|
+
}),
|
|
43
|
+
]);
|
|
44
|
+
status = response.status;
|
|
45
|
+
} catch {
|
|
46
|
+
/* Retain the durable unknown claim for idempotent retry. */
|
|
47
|
+
} finally {
|
|
48
|
+
if (timer) clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
await db.execute(
|
|
51
|
+
sql`select settle_managed_sign_in_notification(${claim.operationId}::uuid,${claim.leaseId}::uuid,${status})`,
|
|
52
|
+
);
|
|
53
|
+
return status;
|
|
54
|
+
} catch {
|
|
55
|
+
// Security state is already committed. A failed claim/ack must never be
|
|
56
|
+
// reported as a failed method change; its durable obligation remains live.
|
|
57
|
+
return "outcome_unknown";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function startManagedSignInNotificationDelivery(
|
|
62
|
+
db: Database,
|
|
63
|
+
transport: ManagedEmailTransport,
|
|
64
|
+
): void {
|
|
65
|
+
if (started.has(db as object)) return;
|
|
66
|
+
started.add(db as object);
|
|
67
|
+
let active = false;
|
|
68
|
+
const drain = async () => {
|
|
69
|
+
if (active) return;
|
|
70
|
+
active = true;
|
|
71
|
+
try {
|
|
72
|
+
for (let count = 0; count < 10; count++)
|
|
73
|
+
if ((await deliverManagedSignInNotification(db, transport)) === "outcome_unknown") break;
|
|
74
|
+
} finally {
|
|
75
|
+
active = false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const timer = setInterval(() => void drain(), 60_000);
|
|
79
|
+
(timer as ReturnType<typeof setInterval> & { unref?: () => void }).unref?.();
|
|
80
|
+
void drain();
|
|
81
|
+
}
|
|
@@ -85,13 +85,7 @@ const RESERVED_MACHINE_SUBJECT_NAMESPACES = [
|
|
|
85
85
|
export function isPersonalConnectionOwnerPrincipal(access: AccessGrantAuthorization): boolean {
|
|
86
86
|
const { grant } = access;
|
|
87
87
|
return (
|
|
88
|
-
//
|
|
89
|
-
// the same question: every context grant agrees on principal kind, delegated
|
|
90
|
-
// flag and service flags, and the selected grant's account resolves to
|
|
91
|
-
// exactly one matching account grant. Without it a grant whose account has
|
|
92
|
-
// no matching account grant (a surviving membership row in an organization
|
|
93
|
-
// where the membership is no longer active) could mint a personal
|
|
94
|
-
// Connection that the sibling helper would refuse.
|
|
88
|
+
// Every context grant must agree on the authenticated principal and account.
|
|
95
89
|
access.contextIntegrity &&
|
|
96
90
|
access.authenticatedSubjectId === grant.subjectId &&
|
|
97
91
|
grant.principalKind === "human_session" &&
|
|
@@ -132,8 +126,7 @@ export function personalOnlyConnectionPrincipalMessage(label: string): string {
|
|
|
132
126
|
* rejects every reserved machine-subject namespace as defence-in-depth. The
|
|
133
127
|
* sibling guards a self-service authority surface where the caller claims to
|
|
134
128
|
* *be* the owner (403 "not you"), while this one rejects an ownership *value*
|
|
135
|
-
* that is unavailable to the caller
|
|
136
|
-
* existing 422 convention.
|
|
129
|
+
* that is unavailable to the caller (422).
|
|
137
130
|
*/
|
|
138
131
|
export function assertPersonalConnectionOwnerPrincipal(
|
|
139
132
|
access: AccessGrantAuthorization,
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
PostgresEditableArtifactStore,
|
|
48
48
|
listEditableArtifactIdsForSession,
|
|
49
49
|
touchEditableArtifactSessionLink,
|
|
50
|
+
recordNativeDocumentInspection,
|
|
50
51
|
dbSql,
|
|
51
52
|
withRlsContext,
|
|
52
53
|
type Database,
|
|
@@ -167,6 +168,11 @@ export async function createStandaloneEditableArtifactApplication(input: {
|
|
|
167
168
|
await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId),
|
|
168
169
|
} satisfies EditableArtifactAgentAssociationPort),
|
|
169
170
|
inspector: kernel,
|
|
171
|
+
recordInspection: async (inspection) => {
|
|
172
|
+
if (inspection.actor.kind !== "agent")
|
|
173
|
+
throw new Error("Inspection proof requires an agent attempt");
|
|
174
|
+
return recordNativeDocumentInspection(input.db, { ...inspection, actor: inspection.actor });
|
|
175
|
+
},
|
|
170
176
|
officeImports,
|
|
171
177
|
workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
|
|
172
178
|
db: input.db,
|