@opengeni/api-router 2.1.0-canary.0 → 2.3.2-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.js +1 -1
- package/dist/auth/managed-auth.d.ts +5 -2
- package/dist/auth/managed-email.d.ts +29 -0
- package/dist/auth/organization-user-setup.d.ts +57 -0
- package/dist/{chunk-QKDFBBUE.js → chunk-IBV7Z6F4.js} +3872 -1845
- package/dist/chunk-IBV7Z6F4.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/integrations/slack-app-home.d.ts +1 -1
- package/dist/integrations/slack-bot.d.ts +8 -0
- package/dist/integrations/slack-interactions.d.ts +35 -2
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/session-view.d.ts +1 -0
- package/dist/routes/automations.d.ts +13 -0
- package/dist/routes/insights.d.ts +2 -1
- package/dist/routes/managed-onboarding.d.ts +29 -0
- package/dist/routes/pr-review-github.d.ts +3 -0
- package/package.json +18 -18
- package/src/app.ts +64 -5
- package/src/auth/managed-auth.ts +29 -34
- package/src/auth/managed-email.ts +174 -0
- package/src/auth/organization-user-setup.ts +217 -0
- package/src/http/auth.ts +15 -0
- package/src/http/sse.ts +62 -13
- package/src/integrations/slack-app-home.ts +2 -2
- package/src/integrations/slack-bot.ts +5 -0
- package/src/integrations/slack-interactions.ts +653 -71
- package/src/integrations/slack-routing.ts +25 -12
- package/src/mcp/company-brain-governed-writes.ts +4 -4
- package/src/mcp/company-profile-agent-admin.ts +11 -18
- package/src/mcp/remember.ts +4 -4
- package/src/mcp/server.ts +50 -4
- package/src/mcp/session-view.ts +8 -2
- package/src/routes/automations.ts +3 -3
- package/src/routes/documents.ts +136 -3
- package/src/routes/insights.ts +61 -19
- package/src/routes/managed-onboarding.ts +317 -0
- package/src/routes/organization-memberships.ts +212 -155
- package/src/routes/pr-review-github.ts +844 -0
- package/src/routes/pr-review.ts +20 -0
- package/src/routes/rigs.ts +37 -4
- package/src/routes/sessions.ts +101 -0
- package/src/sandbox/channel-a.ts +34 -7
- package/src/sandbox/viewer.ts +47 -11
- package/dist/chunk-QKDFBBUE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -75,6 +75,14 @@ export type SlackMessageBlock = {
|
|
|
75
75
|
};
|
|
76
76
|
style?: "primary" | "danger";
|
|
77
77
|
}>;
|
|
78
|
+
} | {
|
|
79
|
+
type: "context";
|
|
80
|
+
block_id?: string;
|
|
81
|
+
elements: Array<{
|
|
82
|
+
type: "mrkdwn" | "plain_text";
|
|
83
|
+
text: string;
|
|
84
|
+
emoji?: boolean;
|
|
85
|
+
}>;
|
|
78
86
|
} | {
|
|
79
87
|
type: "divider";
|
|
80
88
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type FileResourceRef, type WorkspaceSlackReactionSummonSettings } from "@opengeni/contracts";
|
|
2
|
-
import { type SlackInstallationRoute, type SlackInteractionInboxEntry, type SlackInteractionTriggerKind } from "@opengeni/db";
|
|
2
|
+
import { type SlackInstallationRoute, type SlackInteraction, type SlackInteractionInboxEntry, type SlackInteractionTriggerKind } from "@opengeni/db";
|
|
3
3
|
import { type ApiRouteDeps } from "@opengeni/core";
|
|
4
4
|
import type { Hono } from "hono";
|
|
5
|
-
import { type OpenGeniSlackBotClient } from "./slack-bot.js";
|
|
5
|
+
import { type OpenGeniSlackBotClient, type SlackMessageBlock } from "./slack-bot.js";
|
|
6
6
|
import { type ImportedSlackReactionImage } from "../slack-reaction-files.js";
|
|
7
7
|
export declare const SLACK_INTERACTION_MAX_BODY_BYTES: number;
|
|
8
8
|
export declare const SLACK_SIGNATURE_REPLAY_WINDOW_SECONDS = 300;
|
|
@@ -53,6 +53,39 @@ export declare function startSlackInteractionPump(deps: ApiRouteDeps, options?:
|
|
|
53
53
|
intervalMs?: number;
|
|
54
54
|
maxPerTick?: number;
|
|
55
55
|
}): () => void;
|
|
56
|
+
/**
|
|
57
|
+
* Say where the work went, quietly.
|
|
58
|
+
*
|
|
59
|
+
* The line exists to stop work landing somewhere surprising, so it appears only
|
|
60
|
+
* when the routing decision was a genuine choice: a null label means routing is
|
|
61
|
+
* off, or the person had exactly one workspace anyway, and a constant footer on
|
|
62
|
+
* every message would be noise rather than information.
|
|
63
|
+
*
|
|
64
|
+
* The label is frozen on the interaction rather than looked up here, because a
|
|
65
|
+
* workspace rename between the original post and a reconciliation would make the
|
|
66
|
+
* post ledger's byte comparison raise.
|
|
67
|
+
*/
|
|
68
|
+
export declare function withWorkspaceLine(label: string | null, text: string, blocks?: SlackMessageBlock[]): {
|
|
69
|
+
text: string;
|
|
70
|
+
blocks?: SlackMessageBlock[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The post-ledger seed for one interaction's messages.
|
|
74
|
+
*
|
|
75
|
+
* The ledger binds one operation id to a digest over the rendered bytes, so
|
|
76
|
+
* adding the workspace line under an unchanged id would wedge every interaction
|
|
77
|
+
* with a claimed-but-unposted row. But bumping the id for interactions whose
|
|
78
|
+
* bytes did NOT change is not free either: their ledger rows become unreachable,
|
|
79
|
+
* so a repair or a cross-deploy retry posts a duplicate instead of finding the
|
|
80
|
+
* completed original, and an action handle still points at the old operation.
|
|
81
|
+
*
|
|
82
|
+
* Only a labelled interaction renders different bytes, and the label is written
|
|
83
|
+
* once at insert and never updated, so it is a stable discriminator. Version the
|
|
84
|
+
* id on the label rather than on the release: a labelled interaction can only
|
|
85
|
+
* have been created by an image that already renders the line, and every
|
|
86
|
+
* pre-existing and unlabelled interaction keeps its exact ledger identity.
|
|
87
|
+
*/
|
|
88
|
+
export declare function slackPostSeed(interaction: Pick<SlackInteraction, "routedWorkspaceLabel">, seed: string): string;
|
|
56
89
|
export type SlackInvocationMessageContext = {
|
|
57
90
|
messages: Awaited<ReturnType<OpenGeniSlackBotClient["threadReplies"]>>["messages"];
|
|
58
91
|
nextCursor: string | null;
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -95,7 +95,7 @@ export declare function capSessionDiscoveryPage(page: Awaited<ReturnType<typeof
|
|
|
95
95
|
truncated: boolean;
|
|
96
96
|
};
|
|
97
97
|
latestMessage?: {
|
|
98
|
-
type: "agent.message.completed" | "agent.message.delta" | "agent.model.request" | "agent.model.usage" | "agent.reasoning.delta" | "agent.toolCall.created" | "agent.toolCall.output" | "agent.updated" | "artifact.created" | "codex.account.switched" | "codex.capacity.resumed" | "codex.capacity.superseded" | "codex.capacity.waiting" | "codex.credential.selected" | "codex.fleet.decision" | "credential.auth_needed" | "fs.changed" | "git.changed" | "goal.cleared" | "goal.completed" | "goal.continuation" | "goal.held" | "goal.paused" | "goal.progress" | "goal.resumed" | "goal.rewrite.proposed" | "goal.rewrite.rejected" | "goal.set" | "goal.updated" | "machine.link.lost" | "machine.link.restored" | "machine.op.failed" | "machine.op.recovered" | "machine.runner.restarted" | "memory.corrected" | "memory.saved" | "recording.available" | "recording.failed" | "recording.started" | "rig.setup.completed" | "rig.setup.failed" | "rig.setup.skipped" | "rig.setup.started" | "sandbox.box.created" | "sandbox.box.lost" | "sandbox.box.snapshot" | "sandbox.box.terminated" | "sandbox.command.output.delta" | "sandbox.env.drift" | "sandbox.operation.completed" | "sandbox.operation.failed" | "sandbox.operation.started" | "session.command.backgrounded" | "session.command.finished" | "session.context.cleared" | "session.context.compacted" | "session.context.compaction.requested" | "session.context.compaction.skipped" | "session.context.compaction.started" | "session.control.paused" | "session.control.resumed" | "session.control.steer_requested" | "session.created" | "session.event.envelope_omitted" | "session.humanInput.requested" | "session.mcp.approval_policy.updated" | "session.personal_resources.attached" | "session.queue.changed" | "session.queue.history" | "session.queue.prompt.cancelled" | "session.realtime.ended" | "session.realtime.started" | "session.requiresAction" | "session.route.reconciled" | "session.status.changed" | "session.title_set" | "session.tool_policy.updated" | "session.visibility.changed" | "stream.closed" | "stream.opened" | "stream.revoked" | "stream.url.rotated" | "system.update.cancelled" | "system.update.delivered" | "system.update.pending" | "system.update.settled" | "system.update.superseded" | "terminal.pty.exited" | "terminal.pty.output.delta" | "terminal.pty.started" | "tool.auth_needed" | "turn.cancelled" | "turn.capacity_waiting" | "turn.completed" | "turn.event.rejected_late" | "turn.failed" | "turn.queued" | "turn.recovery.requested" | "turn.started" | "turn.startup.phase.completed" | "turn.startup.phase.failed" | "turn.startup.phase.started" | "turn.superseded" | "user.approvalDecision" | "user.humanInputResponse" | "user.message" | "user.pause" | "workspace.inference.paused" | "workspace.inference.resumed" | "workspace.revision.captured" | "workspace.revision.degraded";
|
|
98
|
+
type: "agent.message.completed" | "agent.message.delta" | "agent.model.request" | "agent.model.usage" | "agent.reasoning.delta" | "agent.toolCall.created" | "agent.toolCall.output" | "agent.updated" | "artifact.created" | "codex.account.switched" | "codex.capacity.resumed" | "codex.capacity.superseded" | "codex.capacity.waiting" | "codex.credential.selected" | "codex.fleet.decision" | "credential.auth_needed" | "fs.changed" | "git.changed" | "goal.cleared" | "goal.completed" | "goal.continuation" | "goal.held" | "goal.paused" | "goal.progress" | "goal.resumed" | "goal.rewrite.proposed" | "goal.rewrite.rejected" | "goal.set" | "goal.updated" | "machine.link.lost" | "machine.link.restored" | "machine.op.failed" | "machine.op.recovered" | "machine.runner.restarted" | "memory.corrected" | "memory.saved" | "recording.available" | "recording.failed" | "recording.started" | "rig.setup.completed" | "rig.setup.failed" | "rig.setup.skipped" | "rig.setup.started" | "sandbox.box.created" | "sandbox.box.lost" | "sandbox.box.snapshot" | "sandbox.box.terminated" | "sandbox.command.output.delta" | "sandbox.env.drift" | "sandbox.operation.completed" | "sandbox.operation.failed" | "sandbox.operation.started" | "session.command.backgrounded" | "session.command.finished" | "session.context.cleared" | "session.context.compacted" | "session.context.compaction.requested" | "session.context.compaction.skipped" | "session.context.compaction.started" | "session.control.paused" | "session.control.resumed" | "session.control.steer_requested" | "session.created" | "session.event.envelope_omitted" | "session.humanInput.requested" | "session.mcp.approval_policy.updated" | "session.personal_resources.attached" | "session.queue.changed" | "session.queue.history" | "session.queue.prompt.cancelled" | "session.realtime.ended" | "session.realtime.started" | "session.requiresAction" | "session.route.reconciled" | "session.runtime.configured" | "session.status.changed" | "session.title_set" | "session.tool_policy.updated" | "session.variable_sets.updated" | "session.visibility.changed" | "stream.closed" | "stream.opened" | "stream.revoked" | "stream.url.rotated" | "system.update.cancelled" | "system.update.delivered" | "system.update.pending" | "system.update.settled" | "system.update.superseded" | "terminal.pty.exited" | "terminal.pty.output.delta" | "terminal.pty.started" | "tool.auth_needed" | "turn.cancelled" | "turn.capacity_waiting" | "turn.completed" | "turn.event.rejected_late" | "turn.failed" | "turn.queued" | "turn.recovery.requested" | "turn.started" | "turn.startup.phase.completed" | "turn.startup.phase.failed" | "turn.startup.phase.started" | "turn.superseded" | "user.approvalDecision" | "user.humanInputResponse" | "user.message" | "user.pause" | "workspace.inference.paused" | "workspace.inference.resumed" | "workspace.revision.captured" | "workspace.revision.degraded";
|
|
99
99
|
preview: string | null;
|
|
100
100
|
previewTruncated: boolean;
|
|
101
101
|
} | null;
|
|
@@ -88,6 +88,7 @@ export declare function boundSessionDetailMcp(session: Session, effectiveControl
|
|
|
88
88
|
sandboxGroupId: string;
|
|
89
89
|
activeSandboxId: string | null;
|
|
90
90
|
activeEpoch: number;
|
|
91
|
+
variableSetIds: string[];
|
|
91
92
|
variableSetId: string | null;
|
|
92
93
|
environmentId: string | null;
|
|
93
94
|
rigId: string | null;
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
import { AutomationNormalizedEvent } from "@opengeni/contracts";
|
|
2
|
+
import { type AutomationSourceSecret } from "@opengeni/db";
|
|
1
3
|
import { type ApiRouteDeps } from "@opengeni/core";
|
|
2
4
|
import type { Hono } from "hono";
|
|
3
5
|
export declare function registerAutomationRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
6
|
+
export declare function acceptAutomationEvent(deps: ApiRouteDeps, source: AutomationSourceSecret, input: {
|
|
7
|
+
deliveryKey: string;
|
|
8
|
+
requestDigest: string;
|
|
9
|
+
normalizedEvent: AutomationNormalizedEvent;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
accepted: boolean;
|
|
12
|
+
duplicate: boolean;
|
|
13
|
+
ignoredReason: string | null;
|
|
14
|
+
eventId: string | null;
|
|
15
|
+
runIds: string[];
|
|
16
|
+
}>;
|
|
4
17
|
export declare function readAutomationWebhookBody(request: Request, maxBytes: number): Promise<Uint8Array>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { type ApiRouteDeps } from "@opengeni/core";
|
|
1
|
+
import { type ApiRouteDeps, type WorkspaceInsightsFilterField } from "@opengeni/core";
|
|
2
2
|
import type { Hono } from "hono";
|
|
3
|
+
export declare function normalizeWorkspaceInsightsQueryFilter(value: string | null | undefined, field: WorkspaceInsightsFilterField): string | null;
|
|
3
4
|
export declare function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ApiRouteDeps } from "@opengeni/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
export type ManagedOnboardingRouteOptions = {
|
|
4
|
+
accountSetupLimiter?: {
|
|
5
|
+
take(key: string): boolean;
|
|
6
|
+
};
|
|
7
|
+
hashPassword?: (password: string) => Promise<string>;
|
|
8
|
+
};
|
|
9
|
+
export declare function registerManagedOnboardingRoutes(app: Hono, deps: ApiRouteDeps, options?: ManagedOnboardingRouteOptions): void;
|
|
10
|
+
/**
|
|
11
|
+
* Bounded application-tier protection for the public setup bearer endpoint.
|
|
12
|
+
* The global bucket prevents spoofed/high-cardinality client keys from
|
|
13
|
+
* multiplying password-hash work, while the per-client bucket limits bursts.
|
|
14
|
+
* A full key map rejects new clients until an idle bucket can be pruned.
|
|
15
|
+
*/
|
|
16
|
+
export declare class PublicSetupRateLimiter {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly buckets;
|
|
19
|
+
private global;
|
|
20
|
+
constructor(options?: {
|
|
21
|
+
globalCapacity?: number;
|
|
22
|
+
globalRefillPerSecond?: number;
|
|
23
|
+
clientCapacity?: number;
|
|
24
|
+
clientRefillPerSecond?: number;
|
|
25
|
+
maxClientKeys?: number;
|
|
26
|
+
now?: () => number;
|
|
27
|
+
});
|
|
28
|
+
take(key: string): boolean;
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.2-canary.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": {
|
|
@@ -43,23 +43,23 @@
|
|
|
43
43
|
"@hono/zod-validator": "^0.7.6",
|
|
44
44
|
"@llamaindex/liteparse": "^1.5.3",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
|
-
"@opengeni/agent-proto": "^0.5.1-canary.
|
|
47
|
-
"@opengeni/artifact-tool": "^0.3.
|
|
48
|
-
"@opengeni/capabilities": "^0.3.
|
|
49
|
-
"@opengeni/codemode": "^0.4.
|
|
50
|
-
"@opengeni/codex": "^0.2.
|
|
51
|
-
"@opengeni/config": "^0.
|
|
52
|
-
"@opengeni/contracts": "^2.
|
|
53
|
-
"@opengeni/core": "^2.
|
|
54
|
-
"@opengeni/db": "^3.
|
|
55
|
-
"@opengeni/documents": "^0.
|
|
56
|
-
"@opengeni/events": "^0.
|
|
57
|
-
"@opengeni/github": "^0.5.
|
|
58
|
-
"@opengeni/network": "^0.2.
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^1.
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
62
|
-
"@opengeni/xai-subscription": "^0.1.
|
|
46
|
+
"@opengeni/agent-proto": "^0.5.1-canary.2",
|
|
47
|
+
"@opengeni/artifact-tool": "^0.3.7-canary.0",
|
|
48
|
+
"@opengeni/capabilities": "^0.3.1-canary.1",
|
|
49
|
+
"@opengeni/codemode": "^0.4.15-canary.0",
|
|
50
|
+
"@opengeni/codex": "^0.2.19-canary.1",
|
|
51
|
+
"@opengeni/config": "^0.20.1-canary.0",
|
|
52
|
+
"@opengeni/contracts": "^2.5.0-canary.0",
|
|
53
|
+
"@opengeni/core": "^2.4.0-canary.0",
|
|
54
|
+
"@opengeni/db": "^3.4.0-canary.0",
|
|
55
|
+
"@opengeni/documents": "^0.8.2-canary.0",
|
|
56
|
+
"@opengeni/events": "^0.4.0-canary.0",
|
|
57
|
+
"@opengeni/github": "^0.5.5-canary.0",
|
|
58
|
+
"@opengeni/network": "^0.2.3-canary.1",
|
|
59
|
+
"@opengeni/observability": "^0.8.6-canary.0",
|
|
60
|
+
"@opengeni/runtime": "^1.4.0-canary.0",
|
|
61
|
+
"@opengeni/storage": "^0.2.108-canary.0",
|
|
62
|
+
"@opengeni/xai-subscription": "^0.1.2-canary.1",
|
|
63
63
|
"@temporalio/client": "^1.17.0",
|
|
64
64
|
"better-auth": "^1.6.14",
|
|
65
65
|
"hono": "^4.12.18",
|
package/src/app.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
rlsContextForWorkspace,
|
|
41
41
|
withSessionRlsActorContext,
|
|
42
42
|
} from "@opengeni/db";
|
|
43
|
+
import { requireSessionEventDurableFanoutCapability } from "@opengeni/events";
|
|
43
44
|
import { createObservability } from "@opengeni/observability";
|
|
44
45
|
import { createObjectStorage } from "@opengeni/storage";
|
|
45
46
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
@@ -63,6 +64,8 @@ import {
|
|
|
63
64
|
SessionAuthorizationUnavailableError,
|
|
64
65
|
} from "@opengeni/core";
|
|
65
66
|
import { createManagedAuth } from "./auth/managed-auth";
|
|
67
|
+
import { createManagedEmailTransport } from "./auth/managed-email";
|
|
68
|
+
import { assertManagedEmailTransportMetadata } from "./auth/organization-user-setup";
|
|
66
69
|
import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
|
|
67
70
|
import { requireLimit } from "@opengeni/core";
|
|
68
71
|
import { buildOpenGeniMcpServer } from "./mcp/server";
|
|
@@ -132,6 +135,7 @@ import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
|
|
|
132
135
|
import { registerVideoGenerationRoutes } from "./routes/video-generation";
|
|
133
136
|
import { registerCanonicalHumanIdentityRoutes } from "./routes/canonical-human-identities";
|
|
134
137
|
import { registerOrganizationMembershipRoutes } from "./routes/organization-memberships";
|
|
138
|
+
import { registerManagedOnboardingRoutes } from "./routes/managed-onboarding";
|
|
135
139
|
import { registerUserResourceAuthorityRoutes } from "./routes/user-resource-authorities";
|
|
136
140
|
import { registerConnectionAuthorityRoutes } from "./routes/connection-authorities";
|
|
137
141
|
import { projectClientModel } from "./model-catalog";
|
|
@@ -193,7 +197,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
193
197
|
// Pause) run inside API-originated db commands; install the boot-validated
|
|
194
198
|
// rollout flag once for this process.
|
|
195
199
|
configureChildLifecycleNotices({ enabled: deps.settings.childLifecycleNoticesEnabled });
|
|
196
|
-
const
|
|
200
|
+
const managedEmailTransport =
|
|
201
|
+
deps.managedEmailTransport ?? createManagedEmailTransport(deps.settings);
|
|
202
|
+
assertManagedEmailTransportMetadata(managedEmailTransport);
|
|
203
|
+
const managedAuth =
|
|
204
|
+
deps.managedAuth ?? createManagedAuth(deps.settings, deps.db, managedEmailTransport);
|
|
197
205
|
const objectStorage =
|
|
198
206
|
deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
|
|
199
207
|
let documentServices: DocumentServices | null = deps.documentServices ?? null;
|
|
@@ -296,6 +304,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
296
304
|
githubStateSecret:
|
|
297
305
|
deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
|
|
298
306
|
managedAuth,
|
|
307
|
+
managedEmailTransport,
|
|
299
308
|
objectStorage,
|
|
300
309
|
documentIndexer,
|
|
301
310
|
getDocumentServices,
|
|
@@ -486,6 +495,9 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
486
495
|
await next();
|
|
487
496
|
});
|
|
488
497
|
|
|
498
|
+
// These product-owned auth routes must be registered before Better Auth's
|
|
499
|
+
// wildcard handler or the provider returns its own 404 first.
|
|
500
|
+
registerManagedOnboardingRoutes(app, routeDeps);
|
|
489
501
|
if (managedAuth) {
|
|
490
502
|
app.on(["GET", "POST"], "/v1/auth/*", (c) => managedAuth.handler(c.req.raw));
|
|
491
503
|
}
|
|
@@ -1114,19 +1126,23 @@ type ReadinessChecks = Record<ReadinessCheckName, ReadinessCheck>;
|
|
|
1114
1126
|
type ReadinessCheckResult = { ok: boolean; error?: string };
|
|
1115
1127
|
|
|
1116
1128
|
function readinessChecks(deps: AppDependencies): ReadinessChecks {
|
|
1129
|
+
const configuredNatsCheck = deps.readinessChecks?.nats;
|
|
1117
1130
|
return {
|
|
1118
1131
|
db:
|
|
1119
1132
|
deps.readinessChecks?.db ??
|
|
1120
1133
|
(async () => {
|
|
1121
1134
|
await deps.db.execute(dbSql`select 1`);
|
|
1122
1135
|
}),
|
|
1123
|
-
nats:
|
|
1124
|
-
deps.
|
|
1125
|
-
(
|
|
1136
|
+
nats: async () => {
|
|
1137
|
+
requireSessionEventDurableFanoutCapability(deps.bus);
|
|
1138
|
+
if (configuredNatsCheck) {
|
|
1139
|
+
await configuredNatsCheck();
|
|
1140
|
+
} else {
|
|
1126
1141
|
if (deps.bus.isConnected && !deps.bus.isConnected()) {
|
|
1127
1142
|
throw new Error("NATS is not connected");
|
|
1128
1143
|
}
|
|
1129
|
-
}
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1130
1146
|
temporal:
|
|
1131
1147
|
deps.readinessChecks?.temporal ??
|
|
1132
1148
|
deps.workflowClient.check ??
|
|
@@ -1461,6 +1477,10 @@ const routeLabelPatterns: Array<{
|
|
|
1461
1477
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events$/,
|
|
1462
1478
|
label: "/v1/workspaces/:workspaceId/sessions/:id/events",
|
|
1463
1479
|
},
|
|
1480
|
+
{
|
|
1481
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns$/,
|
|
1482
|
+
label: "/v1/workspaces/:workspaceId/sessions/:id/turns",
|
|
1483
|
+
},
|
|
1464
1484
|
{
|
|
1465
1485
|
pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
|
|
1466
1486
|
label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action",
|
|
@@ -1633,6 +1653,22 @@ const routeLabelPatterns: Array<{
|
|
|
1633
1653
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/app-manifest$/,
|
|
1634
1654
|
label: "/v1/workspaces/:workspaceId/github/app-manifest",
|
|
1635
1655
|
},
|
|
1656
|
+
{
|
|
1657
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github$/,
|
|
1658
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github",
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github\/connect$/,
|
|
1662
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github/connect",
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github\/installations\/select$/,
|
|
1666
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github/installations/select",
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/pr-review\/github\/installations\/[^/]+\/configure$/,
|
|
1670
|
+
label: "/v1/workspaces/:workspaceId/pr-review/github/installations/:installationId/configure",
|
|
1671
|
+
},
|
|
1636
1672
|
{
|
|
1637
1673
|
pattern: /^\/v1\/workspaces\/[^/]+\/capabilities$/,
|
|
1638
1674
|
label: "/v1/workspaces/:workspaceId/capabilities",
|
|
@@ -1925,6 +1961,23 @@ const routeLabelPatterns: Array<{
|
|
|
1925
1961
|
pattern: /^\/v1\/github\/oauth\/callback$/,
|
|
1926
1962
|
label: "/v1/github/oauth/callback",
|
|
1927
1963
|
},
|
|
1964
|
+
{ pattern: /^\/v1\/pr-review\/github\/setup$/, label: "/v1/pr-review/github/setup" },
|
|
1965
|
+
{
|
|
1966
|
+
pattern: /^\/v1\/pr-review\/github\/install\/callback$/,
|
|
1967
|
+
label: "/v1/pr-review/github/install/callback",
|
|
1968
|
+
},
|
|
1969
|
+
{
|
|
1970
|
+
pattern: /^\/v1\/pr-review\/github\/oauth\/callback$/,
|
|
1971
|
+
label: "/v1/pr-review/github/oauth/callback",
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
pattern: /^\/v1\/webhooks\/pr-review\/github$/,
|
|
1975
|
+
label: "/v1/webhooks/pr-review/github",
|
|
1976
|
+
},
|
|
1977
|
+
{
|
|
1978
|
+
pattern: /^\/v1\/workspaces\/[^/]+$/,
|
|
1979
|
+
label: "/v1/workspaces/:workspaceId",
|
|
1980
|
+
},
|
|
1928
1981
|
];
|
|
1929
1982
|
|
|
1930
1983
|
export function routeLabel(pathname: string): string {
|
|
@@ -1949,6 +2002,12 @@ export function isApiContractProtectedMutation(method: string, pathname: string)
|
|
|
1949
2002
|
if (!pathname.startsWith("/v1/")) {
|
|
1950
2003
|
return false;
|
|
1951
2004
|
}
|
|
2005
|
+
if (
|
|
2006
|
+
pathname === "/v1/auth/organization-onboarding" ||
|
|
2007
|
+
pathname === "/v1/auth/organization-setup"
|
|
2008
|
+
) {
|
|
2009
|
+
return true;
|
|
2010
|
+
}
|
|
1952
2011
|
if (
|
|
1953
2012
|
pathname.startsWith("/v1/auth/") ||
|
|
1954
2013
|
pathname.startsWith("/v1/webhooks/") ||
|
package/src/auth/managed-auth.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Settings } from "@opengeni/config";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ManagedAuth,
|
|
4
|
+
type ManagedEmailMessage,
|
|
5
|
+
type ManagedEmailTransport,
|
|
6
|
+
} from "@opengeni/core";
|
|
3
7
|
import type { Database } from "@opengeni/db";
|
|
4
8
|
import { ensureManagedAccessForUser } from "@opengeni/db";
|
|
5
9
|
import {
|
|
@@ -9,8 +13,8 @@ import {
|
|
|
9
13
|
} from "@opengeni/db/canonical-human-identities";
|
|
10
14
|
import { betterAuth } from "better-auth";
|
|
11
15
|
import { createEmailVerificationToken } from "better-auth/api";
|
|
16
|
+
import { hashPassword } from "better-auth/crypto";
|
|
12
17
|
import { Pool } from "pg";
|
|
13
|
-
import { Resend } from "resend";
|
|
14
18
|
|
|
15
19
|
import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-admission";
|
|
16
20
|
|
|
@@ -34,7 +38,16 @@ export function managedAuthUserCreateOverride(
|
|
|
34
38
|
return { data: { ...user, emailVerified: true } };
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
/** Keep Better Auth password policy and storage format behind this boundary. */
|
|
42
|
+
export async function hashManagedAuthPassword(password: string): Promise<string> {
|
|
43
|
+
return await hashPassword(password);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createManagedAuth(
|
|
47
|
+
settings: Settings,
|
|
48
|
+
db: Database,
|
|
49
|
+
managedEmailTransport: ManagedEmailTransport,
|
|
50
|
+
): ManagedAuth | null {
|
|
38
51
|
if (settings.productAccessMode !== "managed") {
|
|
39
52
|
return null;
|
|
40
53
|
}
|
|
@@ -147,7 +160,8 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
147
160
|
onExistingUserSignUp: async ({ user }) => {
|
|
148
161
|
if (requireEmailVerification && !user.emailVerified) {
|
|
149
162
|
const url = await verificationUrl(settings, user.email);
|
|
150
|
-
await
|
|
163
|
+
await sendManagedAuthEmail(managedEmailTransport, {
|
|
164
|
+
kind: "email_verification",
|
|
151
165
|
to: user.email,
|
|
152
166
|
subject: "Verify your OpenGeni email",
|
|
153
167
|
text: `Verify your OpenGeni email: ${url}`,
|
|
@@ -156,7 +170,8 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
156
170
|
}
|
|
157
171
|
},
|
|
158
172
|
sendResetPassword: async ({ user, url }) => {
|
|
159
|
-
await
|
|
173
|
+
await sendManagedAuthEmail(managedEmailTransport, {
|
|
174
|
+
kind: "password_reset",
|
|
160
175
|
to: user.email,
|
|
161
176
|
subject: "Reset your OpenGeni password",
|
|
162
177
|
text: `Reset your OpenGeni password: ${url}`,
|
|
@@ -167,7 +182,8 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
167
182
|
emailVerification: {
|
|
168
183
|
sendOnSignUp: requireEmailVerification,
|
|
169
184
|
sendVerificationEmail: async ({ user, url }) => {
|
|
170
|
-
await
|
|
185
|
+
await sendManagedAuthEmail(managedEmailTransport, {
|
|
186
|
+
kind: "email_verification",
|
|
171
187
|
to: user.email,
|
|
172
188
|
subject: "Verify your OpenGeni email",
|
|
173
189
|
text: `Verify your OpenGeni email: ${url}`,
|
|
@@ -180,6 +196,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
180
196
|
email: user.email,
|
|
181
197
|
name: user.name,
|
|
182
198
|
emailVerified: true,
|
|
199
|
+
provisionFallbackOrganization: false,
|
|
183
200
|
});
|
|
184
201
|
},
|
|
185
202
|
},
|
|
@@ -266,6 +283,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
|
|
|
266
283
|
email: user.email,
|
|
267
284
|
name: user.name,
|
|
268
285
|
emailVerified: true,
|
|
286
|
+
provisionFallbackOrganization: false,
|
|
269
287
|
});
|
|
270
288
|
},
|
|
271
289
|
},
|
|
@@ -297,35 +315,12 @@ function betterAuthTrustedOrigins(settings: Settings): string[] {
|
|
|
297
315
|
return [...origins];
|
|
298
316
|
}
|
|
299
317
|
|
|
300
|
-
async function
|
|
301
|
-
|
|
302
|
-
input:
|
|
303
|
-
to: string;
|
|
304
|
-
subject: string;
|
|
305
|
-
text: string;
|
|
306
|
-
html: string;
|
|
307
|
-
},
|
|
318
|
+
export async function sendManagedAuthEmail(
|
|
319
|
+
transport: ManagedEmailTransport,
|
|
320
|
+
input: Omit<ManagedEmailMessage, "from">,
|
|
308
321
|
): Promise<void> {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
console.warn(
|
|
312
|
-
`[opengeni] Skipping email to ${input.to}: OPENGENI_RESEND_API_KEY is not configured`,
|
|
313
|
-
);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
throw new Error("OPENGENI_RESEND_API_KEY is required to send managed auth email");
|
|
317
|
-
}
|
|
318
|
-
const resend = new Resend(settings.resendApiKey);
|
|
319
|
-
const result = await resend.emails.send({
|
|
320
|
-
from: settings.emailFrom,
|
|
321
|
-
to: input.to,
|
|
322
|
-
subject: input.subject,
|
|
323
|
-
text: input.text,
|
|
324
|
-
html: input.html,
|
|
325
|
-
});
|
|
326
|
-
if (result.error) {
|
|
327
|
-
throw new Error(result.error.message);
|
|
328
|
-
}
|
|
322
|
+
const result = await transport.send({ ...input, from: transport.sender });
|
|
323
|
+
if (result.status !== "sent") throw new Error(`managed email ${result.status}`);
|
|
329
324
|
}
|
|
330
325
|
|
|
331
326
|
async function verificationUrl(settings: Settings, email: string): Promise<string> {
|