@opengeni/core 0.11.2 → 0.12.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.
@@ -0,0 +1,130 @@
1
+ import {
2
+ OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
3
+ OPENGENI_SLACK_BOT_REQUIRED_SCOPES,
4
+ OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
5
+ OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
6
+ OpenGeniSlackBotConnectionMetadata,
7
+ type AccessGrant,
8
+ type ConnectionMetadata,
9
+ type OpenGeniSlackBotConnectionMetadata as OpenGeniSlackBotMetadata,
10
+ type Session,
11
+ } from "@opengeni/contracts";
12
+ import {
13
+ getConnectionMetadata,
14
+ type ConnectionMetadataWithVerification,
15
+ type Database,
16
+ } from "@opengeni/db";
17
+ import { HTTPException } from "hono/http-exception";
18
+ import { requirePermission } from "../access";
19
+
20
+ export function openGeniSlackBotMetadata(
21
+ metadata: Record<string, unknown>,
22
+ ): OpenGeniSlackBotMetadata | null {
23
+ const parsed = OpenGeniSlackBotConnectionMetadata.safeParse(metadata);
24
+ return parsed.success ? parsed.data : null;
25
+ }
26
+
27
+ export function isOpenGeniSlackBotConnection(
28
+ connection: ConnectionMetadata &
29
+ Partial<
30
+ Pick<ConnectionMetadataWithVerification, "verifiedInstallAt" | "verifiedInstallVersion">
31
+ >,
32
+ ): boolean {
33
+ const granted = new Set(connection.grantedScopes);
34
+ return (
35
+ connection.verifiedInstallAt != null &&
36
+ connection.verifiedInstallVersion === connection.version &&
37
+ connection.subjectId === null &&
38
+ connection.providerDomain === "slack.com" &&
39
+ connection.kind === "app_install" &&
40
+ granted.size === OPENGENI_SLACK_BOT_REQUIRED_SCOPES.length &&
41
+ OPENGENI_SLACK_BOT_REQUIRED_SCOPES.every((scope) => granted.has(scope)) &&
42
+ openGeniSlackBotMetadata(connection.metadata)?.credentialRole ===
43
+ OPENGENI_SLACK_BOT_CREDENTIAL_ROLE
44
+ );
45
+ }
46
+
47
+ export function hasReservedOpenGeniSlackBotMetadata(
48
+ metadata: Record<string, unknown> | null | undefined,
49
+ ): boolean {
50
+ return (
51
+ metadata?.credentialRole === OPENGENI_SLACK_BOT_CREDENTIAL_ROLE ||
52
+ metadata?.credentialLabel === OPENGENI_SLACK_BOT_CREDENTIAL_LABEL
53
+ );
54
+ }
55
+
56
+ export function hasReservedOpenGeniSlackBotSessionMetadata(
57
+ metadata: Record<string, unknown> | null | undefined,
58
+ ): boolean {
59
+ return Boolean(
60
+ metadata &&
61
+ Object.prototype.hasOwnProperty.call(metadata, OPENGENI_SLACK_BOT_SESSION_METADATA_KEY),
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Internal, pre-authorized lookup used by the scheduler and Slack tool adapter.
67
+ * Public callers must perform their permission check before calling this helper.
68
+ */
69
+ export async function requireOpenGeniSlackBotConnection(
70
+ db: Database,
71
+ workspaceId: string,
72
+ connectionId: string,
73
+ ): Promise<ConnectionMetadata> {
74
+ const connection = await getConnectionMetadata(db, workspaceId, connectionId, null);
75
+ if (!connection || !isOpenGeniSlackBotConnection(connection)) {
76
+ throw new HTTPException(422, {
77
+ message: "slackBotConnectionId must reference an OpenGeni Slack bot connection",
78
+ });
79
+ }
80
+ if (connection.status !== "active") {
81
+ throw new HTTPException(422, {
82
+ message: `OpenGeni Slack bot connection is not active (${connection.status})`,
83
+ });
84
+ }
85
+ return connection;
86
+ }
87
+
88
+ export async function validateOpenGeniSlackBotConnectionSelection(
89
+ db: Database,
90
+ grant: AccessGrant,
91
+ workspaceId: string,
92
+ connectionId: string,
93
+ ): Promise<ConnectionMetadata> {
94
+ requirePermission(grant, "connections:read");
95
+ return await requireOpenGeniSlackBotConnection(db, workspaceId, connectionId);
96
+ }
97
+
98
+ export function scheduledSlackBotConnectionId(
99
+ metadata: Record<string, unknown> | null | undefined,
100
+ ): string | null {
101
+ const value = metadata?.[OPENGENI_SLACK_BOT_SESSION_METADATA_KEY];
102
+ return typeof value === "string" && zUuid(value) ? value : null;
103
+ }
104
+
105
+ /**
106
+ * The scheduler writes the connection pointer together with immutable creator
107
+ * provenance. Requiring both prevents ordinary session metadata from becoming
108
+ * an authorization mechanism for a workspace-shared bot credential.
109
+ */
110
+ export function isTrustedScheduledSlackBotSession(
111
+ session: Pick<Session, "createdBy" | "createdByContext" | "metadata">,
112
+ ): boolean {
113
+ const scheduledTaskId = session.metadata.scheduledTaskId;
114
+ const scheduledTaskRunId = session.metadata.scheduledTaskRunId;
115
+ return (
116
+ session.createdBy?.kind === "service" &&
117
+ session.createdBy.subjectId === "scheduler" &&
118
+ typeof scheduledTaskId === "string" &&
119
+ zUuid(scheduledTaskId) &&
120
+ typeof scheduledTaskRunId === "string" &&
121
+ zUuid(scheduledTaskRunId) &&
122
+ session.createdByContext.scheduledTaskId === scheduledTaskId &&
123
+ session.createdByContext.scheduledTaskRunId === scheduledTaskRunId &&
124
+ scheduledSlackBotConnectionId(session.metadata) !== null
125
+ );
126
+ }
127
+
128
+ function zUuid(value: string): boolean {
129
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
130
+ }
package/src/index.ts CHANGED
@@ -60,6 +60,7 @@ export * from "./domain/resources";
60
60
  export * from "./domain/session-tool-policy";
61
61
  export * from "./domain/scheduled-tasks";
62
62
  export * from "./domain/sessions";
63
+ export * from "./domain/slack-bot";
63
64
  export * from "./domain/workspace-members";
64
65
  export * from "./application/new-session-drafts";
65
66
  export * from "./application/session-commands";