@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/src/routes/plugins.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import pluginSnapshot from "../../../../data/catalog/plugins-snapshot.json";
|
|
2
2
|
import { marketplacePlugin } from "../integrations/marketplace-plugin";
|
|
3
3
|
import { mcpEndpointIdentity } from "@opengeni/contracts";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildCapabilityCatalog,
|
|
6
|
+
createCatalogItem,
|
|
7
|
+
integrationSourceForOrganizationPolicy,
|
|
8
|
+
} from "@opengeni/core";
|
|
9
|
+
import { withOrganizationIntegrationPolicyFence } from "@opengeni/db/organization-integration-policy";
|
|
5
10
|
import { createHash } from "node:crypto";
|
|
6
11
|
|
|
7
12
|
import {
|
|
@@ -43,6 +48,7 @@ import {
|
|
|
43
48
|
getConnectionMetadata,
|
|
44
49
|
getInstalledPluginPackage,
|
|
45
50
|
getPluginPackageUninstallPreview,
|
|
51
|
+
PluginUninstallPreviewChangedError,
|
|
46
52
|
integrationBindingKey,
|
|
47
53
|
installApiIntegration,
|
|
48
54
|
installPluginMcpReference,
|
|
@@ -344,9 +350,14 @@ export function registerPluginRoutes(
|
|
|
344
350
|
|
|
345
351
|
app.get("/v1/workspaces/:workspaceId/plugins/:pluginKey/uninstall-preview", async (c) => {
|
|
346
352
|
const workspaceId = c.req.param("workspaceId");
|
|
347
|
-
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
353
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
348
354
|
const pluginKey = decodeURIComponent(c.req.param("pluginKey"));
|
|
349
|
-
const preview = await getPluginPackageUninstallPreview(
|
|
355
|
+
const preview = await getPluginPackageUninstallPreview(
|
|
356
|
+
deps.db,
|
|
357
|
+
workspaceId,
|
|
358
|
+
pluginKey,
|
|
359
|
+
grant.subjectId,
|
|
360
|
+
);
|
|
350
361
|
return c.json(PluginUninstallPreview.parse({ pluginKey, ...preview }));
|
|
351
362
|
});
|
|
352
363
|
|
|
@@ -373,11 +384,33 @@ export function registerPluginRoutes(
|
|
|
373
384
|
subjectId: grant.subjectId,
|
|
374
385
|
pluginKey,
|
|
375
386
|
expectedInstallationVersion: payload.expectedInstallationVersion,
|
|
387
|
+
...(payload.expectedPreviewToken
|
|
388
|
+
? { expectedPreviewToken: payload.expectedPreviewToken }
|
|
389
|
+
: {}),
|
|
376
390
|
idempotencyKey: payload.idempotencyKey,
|
|
377
391
|
})),
|
|
378
392
|
}),
|
|
379
393
|
);
|
|
380
394
|
} catch (error) {
|
|
395
|
+
if (error instanceof PluginUninstallPreviewChangedError) {
|
|
396
|
+
const preview = error.preview ?? {
|
|
397
|
+
pluginKey,
|
|
398
|
+
...(await getPluginPackageUninstallPreview(
|
|
399
|
+
deps.db,
|
|
400
|
+
workspaceId,
|
|
401
|
+
pluginKey,
|
|
402
|
+
grant.subjectId,
|
|
403
|
+
)),
|
|
404
|
+
};
|
|
405
|
+
return c.json(
|
|
406
|
+
{
|
|
407
|
+
code: error.code,
|
|
408
|
+
message: error.message,
|
|
409
|
+
preview: PluginUninstallPreview.parse(preview),
|
|
410
|
+
},
|
|
411
|
+
409,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
381
414
|
throw pluginMutationHttpError(error);
|
|
382
415
|
}
|
|
383
416
|
});
|
|
@@ -499,6 +532,11 @@ async function resolvePluginPackage(input: {
|
|
|
499
532
|
continue;
|
|
500
533
|
}
|
|
501
534
|
if (component.kind === "integration") {
|
|
535
|
+
const source = await withOrganizationIntegrationPolicyFence(
|
|
536
|
+
input.deps.db,
|
|
537
|
+
input,
|
|
538
|
+
async (_tx, policy) => integrationSourceForOrganizationPolicy(policy, component.source),
|
|
539
|
+
);
|
|
502
540
|
const requestedBinding = input.bindings[component.key];
|
|
503
541
|
const connection = await optionalConnection(
|
|
504
542
|
input.deps,
|
|
@@ -507,7 +545,7 @@ async function resolvePluginPackage(input: {
|
|
|
507
545
|
requestedBinding?.connectionId,
|
|
508
546
|
);
|
|
509
547
|
const resolved = await resolveApiIntegrationPreview({
|
|
510
|
-
source
|
|
548
|
+
source,
|
|
511
549
|
connection: connectionDescriptor(connection),
|
|
512
550
|
transport: input.transport,
|
|
513
551
|
authority: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
2
|
+
import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
|
|
2
3
|
import {
|
|
3
4
|
AUTOMATION_WEBHOOK_MAX_BYTES,
|
|
4
|
-
OPENGENI_PR_REVIEW_PACK_ID,
|
|
5
5
|
PrReviewManagedGitHubSetup,
|
|
6
6
|
type AccessGrant,
|
|
7
7
|
type GitHubInstallationBindingCandidate,
|
|
@@ -9,22 +9,19 @@ import {
|
|
|
9
9
|
} from "@opengeni/contracts";
|
|
10
10
|
import {
|
|
11
11
|
automationRequestDigest,
|
|
12
|
-
getCapabilityPack,
|
|
13
12
|
hasPermission,
|
|
14
|
-
PR_REVIEW_AUTOMATION_TEMPLATE_ID,
|
|
15
|
-
prReviewPackConnectorId,
|
|
16
13
|
requireAutomationAdapter,
|
|
17
14
|
requireAccessGrantAuthorization,
|
|
18
15
|
externalActorContinuationForAuthorization,
|
|
19
16
|
requirePermission,
|
|
20
17
|
verifyPrReviewWebhook,
|
|
21
18
|
type ApiRouteDeps,
|
|
19
|
+
PR_REVIEW_AUTOMATION_SETUP,
|
|
22
20
|
} from "@opengeni/core";
|
|
23
21
|
import {
|
|
24
22
|
AutomationDeliveryConflictError,
|
|
25
23
|
encryptVariableSetValue,
|
|
26
24
|
getAutomationSourceSecret,
|
|
27
|
-
getPackInstallation,
|
|
28
25
|
listPrReviewAppRegistrations,
|
|
29
26
|
listPrReviewRepositoryBindings,
|
|
30
27
|
nestedPostgresSqlState,
|
|
@@ -49,6 +46,10 @@ import {
|
|
|
49
46
|
} from "@opengeni/github";
|
|
50
47
|
import type { Context, Hono } from "hono";
|
|
51
48
|
import { requireLegacyOAuthActor } from "../connection-ownership";
|
|
49
|
+
import {
|
|
50
|
+
integrationCommitGrant,
|
|
51
|
+
type IntegrationCommitGrant,
|
|
52
|
+
} from "../integrations/integration-commit-authority";
|
|
52
53
|
import { deleteCookie, setCookie } from "hono/cookie";
|
|
53
54
|
import { HTTPException } from "hono/http-exception";
|
|
54
55
|
import { githubBrowserBaseUrl } from "../github-browser-flow";
|
|
@@ -131,7 +132,7 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
131
132
|
const workspaceId = c.req.param("workspaceId");
|
|
132
133
|
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:read");
|
|
133
134
|
const grant = access.grant;
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
const missing = prReviewGitHubAppMissingSettings(deps.settings);
|
|
136
137
|
const configured = missing.length === 0;
|
|
137
138
|
const registrations = (
|
|
@@ -203,7 +204,7 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
203
204
|
const state = requireStateQuery(c, "missing OpenGeni Lens installation state");
|
|
204
205
|
const payload = requireFreshState(state, deps, "pr_review_github_authority", workspaceId);
|
|
205
206
|
await requirePrReviewManageGrant(c, deps, workspaceId, payload);
|
|
206
|
-
|
|
207
|
+
|
|
207
208
|
assertManagedCompute(deps);
|
|
208
209
|
requireConfiguredApp(deps);
|
|
209
210
|
const discoveryState = createSignedState(deps.githubStateSecret, {
|
|
@@ -233,7 +234,7 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
233
234
|
throw new HTTPException(400, { message: "invalid OpenGeni Lens installation" });
|
|
234
235
|
}
|
|
235
236
|
await requirePrReviewManageGrant(c, deps, workspaceId, payload);
|
|
236
|
-
|
|
237
|
+
|
|
237
238
|
assertManagedCompute(deps);
|
|
238
239
|
const registrations = await listPrReviewAppRegistrations(
|
|
239
240
|
deps.db,
|
|
@@ -279,7 +280,7 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
279
280
|
const payload = requireFreshState(state, deps, "pr_review_github_install");
|
|
280
281
|
requireStateCookie(c, state);
|
|
281
282
|
await requirePrReviewManageGrant(c, deps, payload.workspaceId!, payload);
|
|
282
|
-
|
|
283
|
+
|
|
283
284
|
assertManagedCompute(deps);
|
|
284
285
|
const setupAction = c.req.query("setup_action");
|
|
285
286
|
if (setupAction === "request") return c.html(setupPendingHtml());
|
|
@@ -336,10 +337,11 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
336
337
|
const payload = requireFreshState(state, deps);
|
|
337
338
|
requireStateCookie(c, state);
|
|
338
339
|
const grant = await requirePrReviewManageGrant(c, deps, payload.workspaceId!, payload);
|
|
339
|
-
|
|
340
|
+
|
|
340
341
|
assertManagedCompute(deps);
|
|
341
342
|
requireConfiguredApp(deps);
|
|
342
343
|
|
|
344
|
+
await withOrganizationIntegrationAcquisition(deps.db, grant, ["github-lens"], async () => {});
|
|
343
345
|
if (payload.intent === "pr_review_github_discovery") {
|
|
344
346
|
let candidates: GitHubInstallationBindingCandidate[] | null;
|
|
345
347
|
try {
|
|
@@ -410,12 +412,8 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
410
412
|
if (repositoryIds.size !== proof.repositories.length) {
|
|
411
413
|
throw new HTTPException(409, { message: "GitHub returned duplicate repository identities" });
|
|
412
414
|
}
|
|
413
|
-
const template =
|
|
414
|
-
|
|
415
|
-
);
|
|
416
|
-
if (!template) {
|
|
417
|
-
throw new HTTPException(503, { message: "PR Review automation template is unavailable" });
|
|
418
|
-
}
|
|
415
|
+
const template = PR_REVIEW_AUTOMATION_SETUP;
|
|
416
|
+
|
|
419
417
|
const encryptionKey = environmentsEncryptionKeyBytes(deps.settings);
|
|
420
418
|
if (!encryptionKey) {
|
|
421
419
|
throw new HTTPException(503, {
|
|
@@ -424,32 +422,38 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
424
422
|
}
|
|
425
423
|
let synchronized;
|
|
426
424
|
try {
|
|
427
|
-
synchronized = await
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
425
|
+
synchronized = await withOrganizationIntegrationAcquisition(
|
|
426
|
+
deps.db,
|
|
427
|
+
grant,
|
|
428
|
+
["github-lens"],
|
|
429
|
+
async (tx) => {
|
|
430
|
+
await grant.authorizeCommit(tx);
|
|
431
|
+
return syncManagedGitHubPrReviewInstallation(tx, {
|
|
432
|
+
accountId: grant.accountId,
|
|
433
|
+
workspaceId: grant.workspaceId,
|
|
434
|
+
installationId,
|
|
435
|
+
providerAccountLogin: proof.installation.accountLogin,
|
|
436
|
+
providerAccountType: proof.installation.accountType as "User" | "Organization",
|
|
437
|
+
githubActorId: proof.actorId,
|
|
438
|
+
authorityKind: proof.authorityKind,
|
|
439
|
+
authorityCheckedAt: new Date(),
|
|
440
|
+
authorityExpiresAt: new Date((payload.iat + bindingStateMaxAgeSeconds) * 1_000),
|
|
441
|
+
authorityNonce: payload.nonce,
|
|
442
|
+
appId: deps.settings.prReviewGithubAppId!,
|
|
443
|
+
webhookSecretEncrypted: encryptVariableSetValue(
|
|
444
|
+
encryptionKey,
|
|
445
|
+
deps.settings.prReviewGithubWebhookSecret!,
|
|
446
|
+
),
|
|
447
|
+
repositories: proof.repositories,
|
|
448
|
+
createdBySubjectId: grant.subjectId,
|
|
449
|
+
|
|
450
|
+
adapterId: template.adapterId,
|
|
451
|
+
eventTypes: template.eventTypes,
|
|
452
|
+
configuration: template.configuration,
|
|
453
|
+
sessionTemplate: template.sessionTemplate,
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
);
|
|
453
457
|
} catch (error) {
|
|
454
458
|
if (error instanceof PrReviewDispatchAuthorityError) {
|
|
455
459
|
throw new HTTPException(409, { message: error.message });
|
|
@@ -492,7 +496,7 @@ export function registerPrReviewGitHubRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
492
496
|
const payload = requireFreshState(state, deps, "pr_review_github_selection", workspaceId);
|
|
493
497
|
requireStateCookie(c, state);
|
|
494
498
|
await requirePrReviewManageGrant(c, deps, workspaceId, payload);
|
|
495
|
-
|
|
499
|
+
|
|
496
500
|
const selected = c.req.query("installation_id");
|
|
497
501
|
if (selected === "new") return redirectToInstallation(c, deps, state);
|
|
498
502
|
const installationId = positiveInteger(selected);
|
|
@@ -518,16 +522,6 @@ function requireConfiguredApp(deps: ApiRouteDeps): void {
|
|
|
518
522
|
}
|
|
519
523
|
}
|
|
520
524
|
|
|
521
|
-
async function requireActivePack(deps: ApiRouteDeps, workspaceId: string) {
|
|
522
|
-
const installation = await getPackInstallation(deps.db, workspaceId, OPENGENI_PR_REVIEW_PACK_ID);
|
|
523
|
-
if (installation?.status !== "active") {
|
|
524
|
-
throw new HTTPException(409, {
|
|
525
|
-
message: "Install and enable the OpenGeni Review Bot Pack first",
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
return installation;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
525
|
function assertManagedCompute(deps: ApiRouteDeps): void {
|
|
532
526
|
if (deps.settings.sandboxBackend === "selfhosted") {
|
|
533
527
|
throw new HTTPException(409, { message: "OpenGeni Lens requires managed compute" });
|
|
@@ -539,17 +533,33 @@ async function requirePrReviewManageGrant(
|
|
|
539
533
|
deps: ApiRouteDeps,
|
|
540
534
|
workspaceId: string,
|
|
541
535
|
state: GitHubSignedStatePayload,
|
|
542
|
-
): Promise<
|
|
543
|
-
let grant:
|
|
536
|
+
): Promise<IntegrationCommitGrant> {
|
|
537
|
+
let grant: IntegrationCommitGrant;
|
|
544
538
|
try {
|
|
545
539
|
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:admin");
|
|
546
540
|
requireLegacyOAuthActor(access);
|
|
547
|
-
grant = access
|
|
541
|
+
grant = await integrationCommitGrant(access, ["workspace:admin", "secrets:write"], {
|
|
542
|
+
settings: deps.settings,
|
|
543
|
+
authorizationHeader: c.req.header("authorization"),
|
|
544
|
+
});
|
|
548
545
|
} catch (error) {
|
|
549
546
|
if (!(error instanceof HTTPException) || error.status !== 401) throw error;
|
|
550
547
|
const handedOff = prReviewBrowserGrantFromState(deps, state, workspaceId);
|
|
551
548
|
if (!handedOff) throw error;
|
|
552
|
-
grant =
|
|
549
|
+
grant = {
|
|
550
|
+
...handedOff,
|
|
551
|
+
authorizeCommit: async () => {
|
|
552
|
+
const current = prReviewBrowserGrantFromState(deps, state, workspaceId);
|
|
553
|
+
if (
|
|
554
|
+
!current ||
|
|
555
|
+
current.accountId !== handedOff.accountId ||
|
|
556
|
+
current.subjectId !== handedOff.subjectId
|
|
557
|
+
)
|
|
558
|
+
throw new HTTPException(403, {
|
|
559
|
+
message: "OpenGeni Lens browser handoff expired or changed",
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
};
|
|
553
563
|
}
|
|
554
564
|
requirePermission(grant, "secrets:write");
|
|
555
565
|
if (grant.accountId !== state.accountId) {
|
package/src/routes/pr-review.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
2
|
+
import { withOrganizationIntegrationAcquisition } from "@opengeni/db/organization-integration-policy";
|
|
2
3
|
import {
|
|
3
4
|
CreatePrReviewAppRegistrationRequest,
|
|
4
5
|
CreatePrReviewRepositoryBindingRequest,
|
|
5
6
|
PrReviewAppRegistration,
|
|
6
7
|
PrReviewRepositoryBinding,
|
|
7
|
-
OPENGENI_PR_REVIEW_PACK_ID,
|
|
8
8
|
UpdatePrReviewAppRegistrationRequest,
|
|
9
9
|
UpdatePrReviewRepositoryBindingRequest,
|
|
10
10
|
} from "@opengeni/contracts";
|
|
@@ -12,17 +12,16 @@ import {
|
|
|
12
12
|
assertWorkspaceModelPolicyAllows,
|
|
13
13
|
canonicalConfiguredModel,
|
|
14
14
|
defaultPrReviewProviderBaseUrl,
|
|
15
|
-
getCapabilityPack,
|
|
16
15
|
workspaceCustomModelReference,
|
|
17
16
|
lockActiveCustomModelForAdmission,
|
|
18
17
|
prReviewWebhookAuthKind,
|
|
19
18
|
normalizePrReviewProviderBaseUrl,
|
|
20
|
-
prReviewPackConnectorId,
|
|
21
|
-
PR_REVIEW_AUTOMATION_TEMPLATE_ID,
|
|
22
19
|
requireAccessGrant,
|
|
20
|
+
requireAccessGrantAuthorization,
|
|
23
21
|
requirePermission,
|
|
24
22
|
resolveWorkspaceCatalogSettings,
|
|
25
23
|
type ApiRouteDeps,
|
|
24
|
+
PR_REVIEW_AUTOMATION_SETUP,
|
|
26
25
|
} from "@opengeni/core";
|
|
27
26
|
import {
|
|
28
27
|
createPrReviewAppRegistration,
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
decryptVariableSetValue,
|
|
33
32
|
encryptVariableSetValue,
|
|
34
33
|
getPrReviewAppRegistrationSecret,
|
|
35
|
-
getPackInstallation,
|
|
36
34
|
listPrReviewAppRegistrations,
|
|
37
35
|
listPrReviewRepositoryBindings,
|
|
38
36
|
nestedPostgresSqlState,
|
|
@@ -48,6 +46,7 @@ import {
|
|
|
48
46
|
verifyPrReviewProviderRepository,
|
|
49
47
|
} from "../integrations/pr-review-provider";
|
|
50
48
|
import { registerPrReviewGitHubRoutes } from "./pr-review-github";
|
|
49
|
+
import { integrationCommitGrant } from "../integrations/integration-commit-authority";
|
|
51
50
|
|
|
52
51
|
export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
53
52
|
const { db, settings } = deps;
|
|
@@ -56,7 +55,7 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
56
55
|
app.get("/v1/workspaces/:workspaceId/pr-review/registrations", async (c) => {
|
|
57
56
|
const workspaceId = c.req.param("workspaceId");
|
|
58
57
|
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
59
|
-
|
|
58
|
+
|
|
60
59
|
return c.json({
|
|
61
60
|
registrations: await listPrReviewAppRegistrations(db, grant.accountId, workspaceId),
|
|
62
61
|
repositories: await listPrReviewRepositoryBindings(db, grant.accountId, workspaceId),
|
|
@@ -67,7 +66,7 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
67
66
|
const workspaceId = c.req.param("workspaceId");
|
|
68
67
|
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
69
68
|
requirePermission(grant, "secrets:write");
|
|
70
|
-
|
|
69
|
+
|
|
71
70
|
assertPrReviewSandboxBackend(deps);
|
|
72
71
|
const payload = CreatePrReviewAppRegistrationRequest.parse(await c.req.json());
|
|
73
72
|
const encryptionKey = requirePrReviewEncryptionKey(deps);
|
|
@@ -94,28 +93,32 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
94
93
|
});
|
|
95
94
|
}
|
|
96
95
|
const registration = await mapPrReviewUniqueConflict(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
withOrganizationIntegrationAcquisition(
|
|
97
|
+
db,
|
|
98
|
+
grant,
|
|
99
|
+
[prReviewIntegrationKey(payload.provider)],
|
|
100
|
+
(tx) =>
|
|
101
|
+
createPrReviewAppRegistration(tx, {
|
|
102
|
+
accountId: grant.accountId,
|
|
103
|
+
workspaceId,
|
|
104
|
+
name: payload.name,
|
|
105
|
+
provider: payload.provider,
|
|
106
|
+
providerBaseUrl,
|
|
107
|
+
appId: payload.appId ?? null,
|
|
108
|
+
credentialKind: payload.credentialKind,
|
|
109
|
+
credentialEncrypted: encryptVariableSetValue(
|
|
110
|
+
encryptionKey,
|
|
111
|
+
payload.privateKey ?? payload.accessToken!,
|
|
112
|
+
),
|
|
113
|
+
accessTokenExpiresAt: payload.accessTokenExpiresAt
|
|
114
|
+
? new Date(payload.accessTokenExpiresAt)
|
|
115
|
+
: null,
|
|
116
|
+
webhookAuthKind: prReviewWebhookAuthKind(payload.provider),
|
|
117
|
+
webhookSecretEncrypted: encryptVariableSetValue(encryptionKey, payload.webhookSecret),
|
|
118
|
+
webhookUsername: payload.webhookUsername ?? null,
|
|
119
|
+
createdBySubjectId: grant.subjectId,
|
|
120
|
+
}),
|
|
121
|
+
),
|
|
119
122
|
"A PR Review registration with this provider and name already exists",
|
|
120
123
|
);
|
|
121
124
|
await recordAuditEvent(db, {
|
|
@@ -138,7 +141,7 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
138
141
|
const workspaceId = c.req.param("workspaceId");
|
|
139
142
|
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
140
143
|
requirePermission(grant, "secrets:write");
|
|
141
|
-
|
|
144
|
+
|
|
142
145
|
const payload = UpdatePrReviewAppRegistrationRequest.parse(await c.req.json());
|
|
143
146
|
const existing = await getPrReviewAppRegistrationSecret(db, {
|
|
144
147
|
accountId: grant.accountId,
|
|
@@ -272,8 +275,12 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
272
275
|
|
|
273
276
|
app.post("/v1/workspaces/:workspaceId/pr-review/repositories", async (c) => {
|
|
274
277
|
const workspaceId = c.req.param("workspaceId");
|
|
275
|
-
const grant = await
|
|
276
|
-
|
|
278
|
+
const grant = await integrationCommitGrant(
|
|
279
|
+
await requireAccessGrantAuthorization(c, deps, workspaceId, "workspace:admin"),
|
|
280
|
+
["workspace:admin"],
|
|
281
|
+
{ settings: deps.settings, authorizationHeader: c.req.header("authorization") },
|
|
282
|
+
);
|
|
283
|
+
|
|
277
284
|
const payload = CreatePrReviewRepositoryBindingRequest.parse(await c.req.json());
|
|
278
285
|
const registration = await getPrReviewAppRegistrationSecret(db, {
|
|
279
286
|
accountId: grant.accountId,
|
|
@@ -284,6 +291,8 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
284
291
|
throw new HTTPException(404, {
|
|
285
292
|
message: "PR Review app registration not found",
|
|
286
293
|
});
|
|
294
|
+
const integrationKey = prReviewIntegrationKey(registration.provider);
|
|
295
|
+
await withOrganizationIntegrationAcquisition(db, grant, [integrationKey], async () => {});
|
|
287
296
|
const repositoryUrl = new URL(payload.repositoryUri);
|
|
288
297
|
let canonicalRepositoryUri = repositoryUrl.href;
|
|
289
298
|
let canonicalRepositoryFullName = payload.repositoryFullName;
|
|
@@ -406,35 +415,33 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
406
415
|
modelId: model,
|
|
407
416
|
})
|
|
408
417
|
: undefined;
|
|
409
|
-
const template =
|
|
410
|
-
|
|
411
|
-
);
|
|
412
|
-
if (!template) {
|
|
413
|
-
throw new HTTPException(503, { message: "PR Review automation template is unavailable" });
|
|
414
|
-
}
|
|
418
|
+
const template = PR_REVIEW_AUTOMATION_SETUP;
|
|
419
|
+
|
|
415
420
|
const binding = await mapPrReviewUniqueConflict(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
421
|
+
withOrganizationIntegrationAcquisition(db, grant, [integrationKey], async (tx) => {
|
|
422
|
+
await grant.authorizeCommit(tx);
|
|
423
|
+
return createPrReviewRepositoryBinding(tx, {
|
|
424
|
+
accountId: grant.accountId,
|
|
425
|
+
workspaceId,
|
|
426
|
+
registrationId: registration.id,
|
|
427
|
+
provider: registration.provider,
|
|
428
|
+
repositoryUri: canonicalRepositoryUri,
|
|
429
|
+
repositoryFullName: canonicalRepositoryFullName,
|
|
430
|
+
providerRepositoryId: canonicalProviderRepositoryId,
|
|
431
|
+
installationId:
|
|
432
|
+
payload.installationId === undefined ? null : String(payload.installationId),
|
|
433
|
+
projectId: canonicalProjectId,
|
|
434
|
+
model,
|
|
435
|
+
additionalInstructions: payload.additionalInstructions ?? null,
|
|
436
|
+
status: payload.status,
|
|
437
|
+
createdBySubjectId: grant.subjectId,
|
|
438
|
+
|
|
439
|
+
adapterId: template.adapterId,
|
|
440
|
+
eventTypes: template.eventTypes,
|
|
441
|
+
configuration: template.configuration,
|
|
442
|
+
sessionTemplate: template.sessionTemplate,
|
|
443
|
+
...(beforeCreateCommit ? { beforeCreateCommit } : {}),
|
|
444
|
+
});
|
|
438
445
|
}),
|
|
439
446
|
"This repository is already bound to the selected PR Review registration",
|
|
440
447
|
);
|
|
@@ -457,7 +464,7 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
457
464
|
app.patch("/v1/workspaces/:workspaceId/pr-review/repositories/:bindingId", async (c) => {
|
|
458
465
|
const workspaceId = c.req.param("workspaceId");
|
|
459
466
|
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
460
|
-
|
|
467
|
+
|
|
461
468
|
const payload = UpdatePrReviewRepositoryBindingRequest.parse(await c.req.json());
|
|
462
469
|
const catalogSettings = (
|
|
463
470
|
await resolveWorkspaceCatalogSettings(db, deps.settings, {
|
|
@@ -547,6 +554,12 @@ export function registerPrReviewRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
547
554
|
});
|
|
548
555
|
}
|
|
549
556
|
|
|
557
|
+
/** PR Review's server-owned provider, not a repository URL or user label,
|
|
558
|
+
* establishes Lens identity. Other providers have no curated acquisition key. */
|
|
559
|
+
function prReviewIntegrationKey(provider: string): "github-lens" | null {
|
|
560
|
+
return provider === "github" ? "github-lens" : null;
|
|
561
|
+
}
|
|
562
|
+
|
|
550
563
|
function prReviewCustomModelCommitGuard(input: {
|
|
551
564
|
settings: ApiRouteDeps["settings"];
|
|
552
565
|
accountId: string;
|
|
@@ -578,16 +591,6 @@ function assertPrReviewSandboxBackend(deps: ApiRouteDeps): void {
|
|
|
578
591
|
}
|
|
579
592
|
}
|
|
580
593
|
|
|
581
|
-
async function requirePrReviewPackActive(db: ApiRouteDeps["db"], workspaceId: string) {
|
|
582
|
-
const installation = await getPackInstallation(db, workspaceId, OPENGENI_PR_REVIEW_PACK_ID);
|
|
583
|
-
if (installation?.status !== "active") {
|
|
584
|
-
throw new HTTPException(409, {
|
|
585
|
-
message: "Install and enable the OpenGeni Review Bot Pack first",
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
return installation;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
594
|
function requirePrReviewEncryptionKey(deps: ApiRouteDeps): Uint8Array {
|
|
592
595
|
const key = environmentsEncryptionKeyBytes(deps.settings);
|
|
593
596
|
if (!key) {
|