@opengeni/api-router 0.21.9 → 0.21.10

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 CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  validateToolRefs,
23
23
  withDefaultEnabledCapabilityMcpTools,
24
24
  workflowIdForSession
25
- } from "./chunk-BESOWOUQ.js";
25
+ } from "./chunk-J4JS2F7L.js";
26
26
  export {
27
27
  API_MAX_REQUEST_BODY_BYTES,
28
28
  allowedCorsOrigin,
@@ -357,7 +357,6 @@ import {
357
357
  listRigs,
358
358
  listRigChangeMonitoringSummaries,
359
359
  listRigVersionMonitoringSummaries,
360
- listSocialConnections,
361
360
  listSocialPosts,
362
361
  recordAuditEvent as recordAuditEvent2,
363
362
  recordSyncedSocialPosts,
@@ -397,6 +396,7 @@ import {
397
396
  import * as z4 from "zod/v4";
398
397
  import {
399
398
  hasPermission as hasPermission4,
399
+ authorizedSocialConnectionsForGrant,
400
400
  requireSessionAuthorization,
401
401
  requireSessionAuthorizationListScope
402
402
  } from "@opengeni/core";
@@ -2227,6 +2227,7 @@ async function startSocialOAuth(deps, context) {
2227
2227
  accountId: context.accountId,
2228
2228
  workspaceId: context.workspaceId,
2229
2229
  subjectId: context.subjectId,
2230
+ ownership: context.payload.ownership,
2230
2231
  provider: provider.id,
2231
2232
  scopes,
2232
2233
  ...verifier && key ? { encryptedPkceVerifier: encryptEnvironmentValue2(key, verifier) } : {},
@@ -2294,7 +2295,7 @@ async function completeSocialOAuthCallback(deps, input) {
2294
2295
  }
2295
2296
  try {
2296
2297
  const grant = await getWorkspaceGrant2(db, state.subjectId, state.workspaceId);
2297
- if (!grant || grant.accountId !== state.accountId || !hasPermission3(grant.permissions, "workspace:admin")) {
2298
+ if (!grant || grant.accountId !== state.accountId || state.ownership === "workspace" && !hasPermission3(grant.permissions, "workspace:admin")) {
2298
2299
  throw new HTTPException4(403, {
2299
2300
  message: "OAuth subject no longer has permission to connect social accounts"
2300
2301
  });
@@ -2329,6 +2330,7 @@ async function completeSocialOAuthCallback(deps, input) {
2329
2330
  const connection = await upsertSocialOAuthConnection(db, {
2330
2331
  accountId: state.accountId,
2331
2332
  workspaceId: state.workspaceId,
2333
+ subjectId: state.ownership === "personal" ? state.subjectId : null,
2332
2334
  provider: provider.id,
2333
2335
  accountHandle: identity.handle,
2334
2336
  accountName: identity.name ?? null,
@@ -2355,7 +2357,12 @@ async function completeSocialOAuthCallback(deps, input) {
2355
2357
  }
2356
2358
  async function freshSocialAccessToken(deps, ref) {
2357
2359
  const { db, settings } = deps;
2358
- const loaded = await loadSocialConnectionCredential(db, ref.workspaceId, ref.connectionId);
2360
+ const loaded = await loadSocialConnectionCredential(
2361
+ db,
2362
+ ref.workspaceId,
2363
+ ref.connectionId,
2364
+ ref.subjectId
2365
+ );
2359
2366
  if (!loaded) {
2360
2367
  throw new Error(`Social connection not found: ${ref.connectionId}`);
2361
2368
  }
@@ -2396,7 +2403,12 @@ async function freshSocialAccessToken(deps, ref) {
2396
2403
  { cause: error }
2397
2404
  );
2398
2405
  }
2399
- const reloaded = await loadSocialConnectionCredential(db, ref.workspaceId, ref.connectionId);
2406
+ const reloaded = await loadSocialConnectionCredential(
2407
+ db,
2408
+ ref.workspaceId,
2409
+ ref.connectionId,
2410
+ ref.subjectId
2411
+ );
2400
2412
  if (reloaded?.credentialEncrypted && reloaded.credentialEncrypted !== loaded.credentialEncrypted) {
2401
2413
  const winner = parseSocialCredentialBundle(
2402
2414
  decryptEnvironmentValue2(key, reloaded.credentialEncrypted)
@@ -2424,6 +2436,7 @@ async function freshSocialAccessToken(deps, ref) {
2424
2436
  const connection = await updateSocialConnectionCredential(db, {
2425
2437
  workspaceId: ref.workspaceId,
2426
2438
  connectionId: ref.connectionId,
2439
+ ...ref.subjectId !== void 0 ? { subjectId: ref.subjectId } : {},
2427
2440
  credentialEncrypted: encryptEnvironmentValue2(key, JSON.stringify(refreshed)),
2428
2441
  status: "connected",
2429
2442
  tokenMetadata: publicTokenMetadata(refreshed)
@@ -2434,6 +2447,7 @@ async function markNeedsReauth(deps, ref) {
2434
2447
  await updateSocialConnectionCredential(deps.db, {
2435
2448
  workspaceId: ref.workspaceId,
2436
2449
  connectionId: ref.connectionId,
2450
+ ...ref.subjectId !== void 0 ? { subjectId: ref.subjectId } : {},
2437
2451
  status: "needs_reauth"
2438
2452
  });
2439
2453
  }
@@ -2616,6 +2630,7 @@ function readSocialOAuthState(raw, settings) {
2616
2630
  accountId: requiredStateString(payload.accountId, "accountId"),
2617
2631
  workspaceId: requiredStateString(payload.workspaceId, "workspaceId"),
2618
2632
  subjectId: requiredStateString(payload.subjectId, "subjectId"),
2633
+ ownership: payload.ownership === "personal" ? "personal" : "workspace",
2619
2634
  provider,
2620
2635
  scopes: Array.isArray(payload.scopes) ? payload.scopes.filter((scope) => typeof scope === "string") : [],
2621
2636
  ...typeof payload.encryptedPkceVerifier === "string" ? { encryptedPkceVerifier: payload.encryptedPkceVerifier } : {},
@@ -6337,6 +6352,19 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6337
6352
  });
6338
6353
  const can = (permission) => hasPermission4(grant.permissions, permission);
6339
6354
  const toolspaceMode = options.toolspace != null;
6355
+ let socialConnectionsPromise;
6356
+ const authorizedSocialConnections = () => socialConnectionsPromise ??= authorizedSocialConnectionsForGrant({
6357
+ db: deps.db,
6358
+ grant,
6359
+ limit: 500
6360
+ });
6361
+ const requireAuthorizedSocialConnection = async (connectionId) => {
6362
+ const authority = (await authorizedSocialConnections()).find(
6363
+ ({ connection }) => connection.id === connectionId
6364
+ );
6365
+ if (!authority) throw new Error(`Unknown or unavailable social connection: ${connectionId}`);
6366
+ return authority;
6367
+ };
6340
6368
  const sessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
6341
6369
  const selectedTools = sessionId !== null && !toolspaceMode ? new Set(
6342
6370
  grant.metadata?.["firstPartyMcpTools"] ?? DEFAULT_FIRST_PARTY_MCP_TOOLS
@@ -6422,11 +6450,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6422
6450
  inputSchema: { limit: z4.number().int().positive().optional() }
6423
6451
  },
6424
6452
  async ({ limit }) => json({
6425
- connections: await listSocialConnections(
6426
- deps.db,
6427
- grant.workspaceId,
6428
- boundedMcpLimit(limit)
6429
- )
6453
+ connections: (await authorizedSocialConnections()).slice(0, boundedMcpLimit(limit)).map(({ connection }) => connection)
6430
6454
  })
6431
6455
  );
6432
6456
  server.registerTool(
@@ -6441,12 +6465,20 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6441
6465
  }
6442
6466
  },
6443
6467
  async ({ connectionIds, since, windowHours, limit }) => {
6468
+ const authorized = await authorizedSocialConnections();
6469
+ const selected = connectionIds?.length ? connectionIds.map((id) => {
6470
+ const match = authorized.find(({ connection }) => connection.id === id);
6471
+ if (!match) throw new Error(`Unknown or unavailable social connection: ${id}`);
6472
+ return match;
6473
+ }) : authorized;
6474
+ const personalSubjectId = selected.find(({ subjectId }) => subjectId)?.subjectId ?? null;
6444
6475
  const sinceDate = since ? parseMcpDate(since, "since") : new Date(Date.now() - (windowHours ?? 24) * 60 * 60 * 1e3);
6445
6476
  return json({
6446
6477
  since: sinceDate.toISOString(),
6447
6478
  posts: await listSocialPosts(deps.db, {
6448
6479
  workspaceId: grant.workspaceId,
6449
- ...connectionIds?.length ? { connectionIds } : {},
6480
+ subjectId: personalSubjectId,
6481
+ connectionIds: selected.map(({ connection }) => connection.id),
6450
6482
  since: sinceDate,
6451
6483
  limit: boundedMcpLimit(limit)
6452
6484
  })
@@ -6466,7 +6498,8 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6466
6498
  }
6467
6499
  },
6468
6500
  async ({ connectionIds, documentBaseIds, since, windowHours, limit }) => {
6469
- const allConnections = await listSocialConnections(deps.db, grant.workspaceId, 500);
6501
+ const authorized = await authorizedSocialConnections();
6502
+ const allConnections = authorized.map(({ connection }) => connection);
6470
6503
  const selectedIds = connectionIds && connectionIds.length > 0 ? new Set(connectionIds) : null;
6471
6504
  const connections = selectedIds ? allConnections.filter((connection) => selectedIds.has(connection.id)) : allConnections.filter((connection) => connection.status === "connected");
6472
6505
  if (selectedIds) {
@@ -6479,6 +6512,9 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6479
6512
  const sinceDate = since ? parseMcpDate(since, "since") : new Date(Date.now() - (windowHours ?? 24) * 60 * 60 * 1e3);
6480
6513
  const posts = connections.length > 0 ? await listSocialPosts(deps.db, {
6481
6514
  workspaceId: grant.workspaceId,
6515
+ subjectId: authorized.find(
6516
+ ({ connection, subjectId }) => subjectId && connections.some((selected) => selected.id === connection.id)
6517
+ )?.subjectId ?? null,
6482
6518
  connectionIds: connections.map((connection) => connection.id),
6483
6519
  since: sinceDate,
6484
6520
  limit: boundedMcpLimit(limit)
@@ -6512,9 +6548,10 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6512
6548
  }
6513
6549
  },
6514
6550
  async ({ connectionId, query, subreddit, limit }) => {
6551
+ const authority = await requireAuthorizedSocialConnection(connectionId);
6515
6552
  const result = await socialSearchLive(
6516
6553
  deps,
6517
- { workspaceId: grant.workspaceId, connectionId },
6554
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
6518
6555
  { query, subreddit, limit }
6519
6556
  );
6520
6557
  return json({ provider: result.connection.provider, posts: result.posts });
@@ -6531,9 +6568,10 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6531
6568
  }
6532
6569
  },
6533
6570
  async ({ connectionId, sinceId, limit }) => {
6571
+ const authority = await requireAuthorizedSocialConnection(connectionId);
6534
6572
  const result = await socialMentionsLive(
6535
6573
  deps,
6536
- { workspaceId: grant.workspaceId, connectionId },
6574
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
6537
6575
  { sinceId, limit }
6538
6576
  );
6539
6577
  return json({ provider: result.connection.provider, posts: result.posts });
@@ -6550,9 +6588,10 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6550
6588
  }
6551
6589
  },
6552
6590
  async ({ connectionId, id, limit }) => {
6591
+ const authority = await requireAuthorizedSocialConnection(connectionId);
6553
6592
  const result = await socialThreadLive(
6554
6593
  deps,
6555
- { workspaceId: grant.workspaceId, connectionId },
6594
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
6556
6595
  { id, limit }
6557
6596
  );
6558
6597
  return json({ provider: result.connection.provider, posts: result.posts });
@@ -6570,9 +6609,10 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6570
6609
  }
6571
6610
  },
6572
6611
  async ({ connectionId, limit }) => {
6612
+ const authority = await requireAuthorizedSocialConnection(connectionId);
6573
6613
  const result = await socialOwnPostsLive(
6574
6614
  deps,
6575
- { workspaceId: grant.workspaceId, connectionId },
6615
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
6576
6616
  { limit }
6577
6617
  );
6578
6618
  const datedPosts = result.posts.filter((post) => post.createdAt !== null);
@@ -6580,6 +6620,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6580
6620
  accountId: grant.accountId,
6581
6621
  workspaceId: grant.workspaceId,
6582
6622
  connectionId,
6623
+ subjectId: authority.subjectId,
6583
6624
  posts: datedPosts.map((post) => ({
6584
6625
  externalPostId: post.id,
6585
6626
  url: post.url,
@@ -6609,9 +6650,10 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
6609
6650
  }
6610
6651
  },
6611
6652
  async ({ connectionId, inReplyToId, text }) => {
6653
+ const authority = await requireAuthorizedSocialConnection(connectionId);
6612
6654
  const result = await socialPostReply(
6613
6655
  deps,
6614
- { workspaceId: grant.workspaceId, connectionId },
6656
+ { workspaceId: grant.workspaceId, connectionId, subjectId: authority.subjectId },
6615
6657
  { inReplyToId, text }
6616
6658
  );
6617
6659
  await recordAuditEvent2(deps.db, {
@@ -9950,9 +9992,11 @@ function registerCapabilityRoutes(app, deps) {
9950
9992
  const { db, settings } = deps;
9951
9993
  app.get("/v1/workspaces/:workspaceId/capabilities", async (c) => {
9952
9994
  const workspaceId = c.req.param("workspaceId");
9953
- await requireAccessGrant(c, deps, workspaceId, "workspace:read");
9995
+ const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:read");
9954
9996
  return c.json(
9955
- CapabilityCatalogResponse.parse(await buildCapabilityCatalog({ db, workspaceId, settings }))
9997
+ CapabilityCatalogResponse.parse(
9998
+ await buildCapabilityCatalog({ db, workspaceId, settings, subjectId: grant.subjectId })
9999
+ )
9956
10000
  );
9957
10001
  });
9958
10002
  app.post("/v1/workspaces/:workspaceId/capabilities", async (c) => {
@@ -16668,7 +16712,7 @@ import {
16668
16712
  getWorkspacePack,
16669
16713
  getSocialConnection,
16670
16714
  listPackInstallations,
16671
- listSocialConnections as listSocialConnections2,
16715
+ listSocialConnections,
16672
16716
  registerWorkspacePack,
16673
16717
  updatePackInstallationStatus
16674
16718
  } from "@opengeni/db";
@@ -16813,7 +16857,12 @@ function registerPackRoutes(app, deps) {
16813
16857
  action: "schedule:create",
16814
16858
  quantity: 1
16815
16859
  });
16816
- const connections = await resolveSocialConnections(db, workspaceId, payload.connectionIds);
16860
+ const connections = await resolveSocialConnections(
16861
+ db,
16862
+ workspaceId,
16863
+ payload.connectionIds,
16864
+ grant.subjectId
16865
+ );
16817
16866
  if (connections.length === 0) {
16818
16867
  throw new HTTPException21(422, {
16819
16868
  message: "at least one connected social account is required"
@@ -16866,17 +16915,17 @@ async function requirePack(db, workspaceId, packId) {
16866
16915
  }
16867
16916
  return pack;
16868
16917
  }
16869
- async function resolveSocialConnections(db, workspaceId, connectionIds) {
16918
+ async function resolveSocialConnections(db, workspaceId, connectionIds, subjectId) {
16870
16919
  const ids = [...new Set(connectionIds)];
16871
16920
  const connections = ids.length > 0 ? await Promise.all(
16872
16921
  ids.map(async (id) => {
16873
- const connection = await getSocialConnection(db, workspaceId, id);
16922
+ const connection = await getSocialConnection(db, workspaceId, id, subjectId);
16874
16923
  if (!connection) {
16875
16924
  throw new HTTPException21(422, { message: `unknown social connection: ${id}` });
16876
16925
  }
16877
16926
  return connection;
16878
16927
  })
16879
- ) : (await listSocialConnections2(db, workspaceId, 500)).filter(
16928
+ ) : (await listSocialConnections(db, workspaceId, 500, subjectId)).filter(
16880
16929
  (connection) => connection.status === "connected"
16881
16930
  );
16882
16931
  const inactive = connections.find((connection) => connection.status !== "connected");
@@ -21471,7 +21520,8 @@ import {
21471
21520
  import {
21472
21521
  createSocialConnection,
21473
21522
  createSocialPost,
21474
- listSocialConnections as listSocialConnections3,
21523
+ getSocialConnection as getSocialConnection2,
21524
+ listSocialConnections as listSocialConnections2,
21475
21525
  listSocialPosts as listSocialPosts2,
21476
21526
  updateSocialConnectionCredential as updateSocialConnectionCredential2
21477
21527
  } from "@opengeni/db";
@@ -21482,8 +21532,15 @@ function registerSocialRoutes(app, deps) {
21482
21532
  const { db, settings, observability } = deps;
21483
21533
  app.get("/v1/workspaces/:workspaceId/social/connections", async (c) => {
21484
21534
  const workspaceId = c.req.param("workspaceId");
21485
- await requireAccessGrant15(c, deps, workspaceId, "workspace:read");
21486
- return c.json(await listSocialConnections3(db, workspaceId, boundedLimit(c.req.query("limit"))));
21535
+ const grant = await requireAccessGrant15(c, deps, workspaceId, "workspace:read");
21536
+ return c.json(
21537
+ await listSocialConnections2(
21538
+ db,
21539
+ workspaceId,
21540
+ boundedLimit(c.req.query("limit")),
21541
+ grant.subjectId
21542
+ )
21543
+ );
21487
21544
  });
21488
21545
  app.post("/v1/workspaces/:workspaceId/social/connections", async (c) => {
21489
21546
  const workspaceId = c.req.param("workspaceId");
@@ -21512,10 +21569,16 @@ function registerSocialRoutes(app, deps) {
21512
21569
  });
21513
21570
  app.delete("/v1/workspaces/:workspaceId/social/connections/:connectionId", async (c) => {
21514
21571
  const workspaceId = c.req.param("workspaceId");
21515
- await requireAccessGrant15(c, deps, workspaceId, "workspace:admin");
21572
+ const grant = await requireAccessGrant15(c, deps, workspaceId, "workspace:read");
21573
+ const connectionId = c.req.param("connectionId");
21574
+ const existing = await getSocialConnection2(db, workspaceId, connectionId, grant.subjectId);
21575
+ if (!existing) throw new HTTPException26(404, { message: "social connection not found" });
21576
+ if (existing.ownership === "workspace")
21577
+ await requireAccessGrant15(c, deps, workspaceId, "workspace:admin");
21516
21578
  const connection = await updateSocialConnectionCredential2(db, {
21517
21579
  workspaceId,
21518
- connectionId: c.req.param("connectionId"),
21580
+ connectionId,
21581
+ subjectId: existing.ownership === "personal" ? grant.subjectId : null,
21519
21582
  credentialEncrypted: null,
21520
21583
  status: "disabled",
21521
21584
  tokenMetadata: {}
@@ -21527,7 +21590,6 @@ function registerSocialRoutes(app, deps) {
21527
21590
  });
21528
21591
  app.post("/v1/workspaces/:workspaceId/social/oauth/start", async (c) => {
21529
21592
  const workspaceId = c.req.param("workspaceId");
21530
- const grant = await requireAccessGrant15(c, deps, workspaceId, "workspace:admin");
21531
21593
  const parsed = SocialOAuthStartRequest.safeParse(await c.req.json());
21532
21594
  if (!parsed.success) {
21533
21595
  throw new HTTPException26(400, {
@@ -21535,6 +21597,12 @@ function registerSocialRoutes(app, deps) {
21535
21597
  });
21536
21598
  }
21537
21599
  const payload = parsed.data;
21600
+ const grant = await requireAccessGrant15(
21601
+ c,
21602
+ deps,
21603
+ workspaceId,
21604
+ payload.ownership === "workspace" ? "workspace:admin" : "workspace:read"
21605
+ );
21538
21606
  const result = await startSocialOAuth(
21539
21607
  { db, settings, observability },
21540
21608
  {
@@ -21561,7 +21629,7 @@ function registerSocialRoutes(app, deps) {
21561
21629
  });
21562
21630
  app.get("/v1/workspaces/:workspaceId/social/posts", async (c) => {
21563
21631
  const workspaceId = c.req.param("workspaceId");
21564
- await requireAccessGrant15(c, deps, workspaceId, "workspace:read");
21632
+ const grant = await requireAccessGrant15(c, deps, workspaceId, "workspace:read");
21565
21633
  const since = parseSince(c.req.query("since"));
21566
21634
  const connectionIds = parseConnectionIds(
21567
21635
  c.req.query("connectionIds") ?? c.req.query("connectionId")
@@ -21569,6 +21637,7 @@ function registerSocialRoutes(app, deps) {
21569
21637
  return c.json(
21570
21638
  await listSocialPosts2(db, {
21571
21639
  workspaceId,
21640
+ subjectId: grant.subjectId,
21572
21641
  ...connectionIds?.length ? { connectionIds } : {},
21573
21642
  ...since ? { since } : {},
21574
21643
  limit: boundedLimit(c.req.query("limit"))
@@ -21584,6 +21653,7 @@ function registerSocialRoutes(app, deps) {
21584
21653
  await createSocialPost(db, {
21585
21654
  accountId: grant.accountId,
21586
21655
  workspaceId,
21656
+ subjectId: grant.subjectId,
21587
21657
  connectionId: payload.connectionId,
21588
21658
  externalPostId: payload.externalPostId ?? null,
21589
21659
  url: payload.url ?? null,
@@ -26547,4 +26617,4 @@ export {
26547
26617
  withDefaultEnabledCapabilityMcpTools,
26548
26618
  workflowIdForSession2 as workflowIdForSession
26549
26619
  };
26550
- //# sourceMappingURL=chunk-BESOWOUQ.js.map
26620
+ //# sourceMappingURL=chunk-J4JS2F7L.js.map