@opengeni/core 0.14.4 → 0.16.1

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.
@@ -24,8 +24,10 @@ export type ResolvedSessionAuthorization = {
24
24
  * an immediate target id and signed attempt claims, but can never nominate a
25
25
  * lineage root or frozen initiator.
26
26
  *
27
- * Returns null when no host port is bound so standalone behavior stays byte-
28
- * for-byte unchanged and pays no additional lineage lookup.
27
+ * Slack-owned private sessions are enforced here even when no embedding-host
28
+ * authorization port is bound. That durable ownership fence covers every
29
+ * session surface which uses this shared seam, rather than relying on list/UI
30
+ * filtering or caller-controlled session metadata.
29
31
  */
30
32
  export declare function requireSessionAuthorization(deps: SessionAuthorizationDependencies, grant: AccessGrant, input: {
31
33
  sessionId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "0.14.4",
3
+ "version": "0.16.1",
4
4
  "description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -35,14 +35,14 @@
35
35
  "dependencies": {
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
37
  "@opengeni/codex": "^0.2.9",
38
- "@opengeni/config": "^0.7.22",
39
- "@opengeni/contracts": "^0.26.1",
40
- "@opengeni/db": "^0.16.2",
41
- "@opengeni/documents": "^0.2.59",
42
- "@opengeni/events": "^0.3.50",
38
+ "@opengeni/config": "^0.9.1",
39
+ "@opengeni/contracts": "^0.28.1",
40
+ "@opengeni/db": "^0.18.1",
41
+ "@opengeni/documents": "^0.2.63",
42
+ "@opengeni/events": "^0.3.54",
43
43
  "@opengeni/observability": "^0.3.0",
44
- "@opengeni/runtime": "^0.14.16",
45
- "@opengeni/storage": "^0.2.46",
44
+ "@opengeni/runtime": "^0.15.3",
45
+ "@opengeni/storage": "^0.2.50",
46
46
  "hono": "^4.12.18"
47
47
  },
48
48
  "engines": {
@@ -1,4 +1,4 @@
1
- import type { Settings } from "@opengeni/config";
1
+ import { resolveFirstPartyDelegationSecret, type Settings } from "@opengeni/config";
2
2
  import {
3
3
  verifyDelegatedAccessToken,
4
4
  type AccountGrant,
@@ -137,7 +137,9 @@ export function requirePermission(grant: AccessGrant, permission: Permission): v
137
137
  message: "missing permission: variable-sets:manage (deprecated alias: environments:manage)",
138
138
  });
139
139
  }
140
- throw new HTTPException(403, { message: `missing permission: ${permission}` });
140
+ throw new HTTPException(403, {
141
+ message: `missing permission: ${permission}`,
142
+ });
141
143
  }
142
144
  }
143
145
 
@@ -208,7 +210,9 @@ async function resolveAccessContext(c: Context, deps: AccessDeps): Promise<Acces
208
210
  }
209
211
 
210
212
  if (deps.managedAuth) {
211
- const session = await deps.managedAuth.api.getSession({ headers: c.req.raw.headers });
213
+ const session = await deps.managedAuth.api.getSession({
214
+ headers: c.req.raw.headers,
215
+ });
212
216
  if (session?.user) {
213
217
  return await ensureManagedAccessForUser(deps.db, {
214
218
  userId: session.user.id,
@@ -275,10 +279,11 @@ async function delegatedAccessContext(
275
279
  mode: "local" | "configured" | "managed",
276
280
  token = bearerToken(c),
277
281
  ): Promise<AccessContext | null> {
278
- if (!token || !deps.settings.delegationSecret) {
282
+ const delegationSecret = resolveFirstPartyDelegationSecret(deps.settings);
283
+ if (!token || !delegationSecret) {
279
284
  return null;
280
285
  }
281
- const payload = await verifyDelegatedAccessToken(deps.settings.delegationSecret, token);
286
+ const payload = await verifyDelegatedAccessToken(delegationSecret, token);
282
287
  if (!payload) {
283
288
  return null;
284
289
  }
@@ -36,10 +36,26 @@ const marketingSocialPack: CapabilityPack = {
36
36
  category: "social-media",
37
37
  authModel: "oauth2_authorization_code_pkce",
38
38
  providers: ["x"],
39
- scopes: ["tweet.read", "users.read", "offline.access"],
39
+ scopes: ["tweet.read", "tweet.write", "users.read", "offline.access"],
40
40
  required: false,
41
41
  metadata: {
42
42
  docs: "https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code",
43
+ firstParty: true,
44
+ oauthStartPath: "/social/oauth/start",
45
+ },
46
+ },
47
+ {
48
+ id: "reddit",
49
+ name: "Reddit",
50
+ category: "social-media",
51
+ authModel: "oauth2_authorization_code",
52
+ providers: ["reddit"],
53
+ scopes: ["identity", "read", "submit", "privatemessages", "history"],
54
+ required: false,
55
+ metadata: {
56
+ docs: "https://github.com/reddit-archive/reddit/wiki/OAuth2",
57
+ firstParty: true,
58
+ oauthStartPath: "/social/oauth/start",
43
59
  },
44
60
  },
45
61
  {
@@ -130,6 +146,11 @@ const marketingSocialPack: CapabilityPack = {
130
146
  "social_connections_list",
131
147
  "social_posts_recent",
132
148
  "social_daily_analysis_context",
149
+ "social_search_live",
150
+ "social_mentions_live",
151
+ "social_thread_fetch",
152
+ "social_posts_sync",
153
+ "social_post_reply",
133
154
  ],
134
155
  },
135
156
  };
@@ -1350,7 +1350,7 @@ export async function createSessionForRequest(
1350
1350
  }
1351
1351
  // Tool visibility is independent from permission authority. A child that
1352
1352
  // omits the field inherits the parent's exact effective selection; a
1353
- // top-level omission selects the complete catalog.
1353
+ // top-level omission selects the safe non-connector default catalog.
1354
1354
  const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
1355
1355
  payload.firstPartyMcpTools,
1356
1356
  parentSession ? parentSession.firstPartyMcpTools : undefined,
@@ -11,6 +11,7 @@ import {
11
11
  getSession,
12
12
  getSessionRootId,
13
13
  getSessionTurnForAttempt,
14
+ getSlackInteractionSessionAccessForSession,
14
15
  type Database,
15
16
  } from "@opengeni/db";
16
17
  import type { AppDependencies } from "./dependencies";
@@ -51,8 +52,10 @@ export type ResolvedSessionAuthorization = {
51
52
  * an immediate target id and signed attempt claims, but can never nominate a
52
53
  * lineage root or frozen initiator.
53
54
  *
54
- * Returns null when no host port is bound so standalone behavior stays byte-
55
- * for-byte unchanged and pays no additional lineage lookup.
55
+ * Slack-owned private sessions are enforced here even when no embedding-host
56
+ * authorization port is bound. That durable ownership fence covers every
57
+ * session surface which uses this shared seam, rather than relying on list/UI
58
+ * filtering or caller-controlled session metadata.
56
59
  */
57
60
  export async function requireSessionAuthorization(
58
61
  deps: SessionAuthorizationDependencies,
@@ -64,12 +67,26 @@ export async function requireSessionAuthorization(
64
67
  },
65
68
  ): Promise<ResolvedSessionAuthorization | null> {
66
69
  const port = deps.sessionAuthorization;
70
+ const slackAccess = await getSlackInteractionSessionAccessForSession(deps.db, {
71
+ accountId: grant.accountId,
72
+ workspaceId: grant.workspaceId,
73
+ sessionId: input.sessionId,
74
+ });
75
+ if (!port && slackAccess?.visibility !== "private") return null;
76
+
77
+ const actor = await resolveSessionAuthorizationActor(deps.db, grant);
78
+ const target = slackAccess
79
+ ? { sessionId: input.sessionId, rootSessionId: slackAccess.rootSessionId }
80
+ : await resolveSessionAuthorizationTarget(deps.db, grant, input.sessionId);
81
+ if (slackAccess?.visibility === "private") {
82
+ const allowed =
83
+ actor.kind === "subject"
84
+ ? actor.subjectId === slackAccess.owningSubjectId
85
+ : actor.callerRootSessionId === target.rootSessionId;
86
+ if (!allowed) throw new SessionAuthorizationDeniedError("forbidden");
87
+ }
67
88
  if (!port) return null;
68
89
 
69
- const [actor, target] = await Promise.all([
70
- resolveSessionAuthorizationActor(deps.db, grant),
71
- resolveSessionAuthorizationTarget(deps.db, grant, input.sessionId),
72
- ]);
73
90
  let rawDecision: unknown;
74
91
  try {
75
92
  rawDecision = await port.authorizeSession({