@opengeni/core 0.14.3 → 0.15.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.
- package/dist/access/index.d.ts +1 -1
- package/dist/index.js +24 -9
- package/dist/index.js.map +1 -1
- package/dist/session-authorization.d.ts +4 -2
- package/package.json +8 -8
- package/src/access/index.ts +10 -5
- package/src/session-authorization.ts +23 -6
|
@@ -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
|
-
*
|
|
28
|
-
*
|
|
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.
|
|
3
|
+
"version": "0.15.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.
|
|
39
|
-
"@opengeni/contracts": "^0.
|
|
40
|
-
"@opengeni/db": "^0.
|
|
41
|
-
"@opengeni/documents": "^0.2.
|
|
42
|
-
"@opengeni/events": "^0.3.
|
|
38
|
+
"@opengeni/config": "^0.8.1",
|
|
39
|
+
"@opengeni/contracts": "^0.27.0",
|
|
40
|
+
"@opengeni/db": "^0.17.1",
|
|
41
|
+
"@opengeni/documents": "^0.2.61",
|
|
42
|
+
"@opengeni/events": "^0.3.52",
|
|
43
43
|
"@opengeni/observability": "^0.3.0",
|
|
44
|
-
"@opengeni/runtime": "^0.
|
|
45
|
-
"@opengeni/storage": "^0.2.
|
|
44
|
+
"@opengeni/runtime": "^0.15.1",
|
|
45
|
+
"@opengeni/storage": "^0.2.48",
|
|
46
46
|
"hono": "^4.12.18"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
package/src/access/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
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, {
|
|
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({
|
|
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
|
-
|
|
282
|
+
const delegationSecret = resolveFirstPartyDelegationSecret(deps.settings);
|
|
283
|
+
if (!token || !delegationSecret) {
|
|
279
284
|
return null;
|
|
280
285
|
}
|
|
281
|
-
const payload = await verifyDelegatedAccessToken(
|
|
286
|
+
const payload = await verifyDelegatedAccessToken(delegationSecret, token);
|
|
282
287
|
if (!payload) {
|
|
283
288
|
return null;
|
|
284
289
|
}
|
|
@@ -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
|
-
*
|
|
55
|
-
*
|
|
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({
|