@lobu/gateway 3.0.9 → 3.0.13
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/api/platform.d.ts.map +1 -1
- package/dist/api/platform.js +7 -26
- package/dist/api/platform.js.map +1 -1
- package/dist/auth/mcp/proxy.d.ts +14 -0
- package/dist/auth/mcp/proxy.d.ts.map +1 -1
- package/dist/auth/mcp/proxy.js +149 -13
- package/dist/auth/mcp/proxy.js.map +1 -1
- package/dist/cli/gateway.d.ts.map +1 -1
- package/dist/cli/gateway.js +29 -0
- package/dist/cli/gateway.js.map +1 -1
- package/dist/connections/chat-instance-manager.d.ts.map +1 -1
- package/dist/connections/chat-instance-manager.js +2 -1
- package/dist/connections/chat-instance-manager.js.map +1 -1
- package/dist/connections/interaction-bridge.d.ts +9 -2
- package/dist/connections/interaction-bridge.d.ts.map +1 -1
- package/dist/connections/interaction-bridge.js +121 -261
- package/dist/connections/interaction-bridge.js.map +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/gateway/index.js.map +1 -1
- package/dist/interactions.d.ts +9 -43
- package/dist/interactions.d.ts.map +1 -1
- package/dist/interactions.js +10 -52
- package/dist/interactions.js.map +1 -1
- package/dist/routes/public/agent.d.ts +4 -0
- package/dist/routes/public/agent.d.ts.map +1 -1
- package/dist/routes/public/agent.js +21 -0
- package/dist/routes/public/agent.js.map +1 -1
- package/dist/services/core-services.d.ts.map +1 -1
- package/dist/services/core-services.js +4 -0
- package/dist/services/core-services.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/agent-config-routes.test.ts +0 -254
- package/src/__tests__/agent-history-routes.test.ts +0 -72
- package/src/__tests__/agent-routes.test.ts +0 -68
- package/src/__tests__/agent-schedules-routes.test.ts +0 -59
- package/src/__tests__/agent-settings-store.test.ts +0 -323
- package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
- package/src/__tests__/bedrock-openai-service.test.ts +0 -157
- package/src/__tests__/bedrock-provider-module.test.ts +0 -56
- package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
- package/src/__tests__/chat-response-bridge.test.ts +0 -131
- package/src/__tests__/config-memory-plugins.test.ts +0 -92
- package/src/__tests__/config-request-store.test.ts +0 -127
- package/src/__tests__/connection-routes.test.ts +0 -144
- package/src/__tests__/core-services-store-selection.test.ts +0 -92
- package/src/__tests__/docker-deployment.test.ts +0 -1211
- package/src/__tests__/embedded-deployment.test.ts +0 -342
- package/src/__tests__/grant-store.test.ts +0 -148
- package/src/__tests__/http-proxy.test.ts +0 -281
- package/src/__tests__/instruction-service.test.ts +0 -37
- package/src/__tests__/link-buttons.test.ts +0 -112
- package/src/__tests__/lobu.test.ts +0 -32
- package/src/__tests__/mcp-config-service.test.ts +0 -347
- package/src/__tests__/mcp-proxy.test.ts +0 -694
- package/src/__tests__/message-handler-bridge.test.ts +0 -17
- package/src/__tests__/model-selection.test.ts +0 -172
- package/src/__tests__/oauth-templates.test.ts +0 -39
- package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
- package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
- package/src/__tests__/provider-inheritance.test.ts +0 -212
- package/src/__tests__/routes/cli-auth.test.ts +0 -337
- package/src/__tests__/routes/interactions.test.ts +0 -121
- package/src/__tests__/secret-proxy.test.ts +0 -85
- package/src/__tests__/session-manager.test.ts +0 -572
- package/src/__tests__/setup.ts +0 -133
- package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
- package/src/__tests__/slack-routes.test.ts +0 -161
- package/src/__tests__/system-config-resolver.test.ts +0 -75
- package/src/__tests__/system-message-limiter.test.ts +0 -89
- package/src/__tests__/system-skills-service.test.ts +0 -362
- package/src/__tests__/transcription-service.test.ts +0 -222
- package/src/__tests__/utils/rate-limiter.test.ts +0 -102
- package/src/__tests__/worker-connection-manager.test.ts +0 -497
- package/src/__tests__/worker-job-router.test.ts +0 -722
- package/src/api/index.ts +0 -1
- package/src/api/platform.ts +0 -292
- package/src/api/response-renderer.ts +0 -157
- package/src/auth/agent-metadata-store.ts +0 -168
- package/src/auth/api-auth-middleware.ts +0 -69
- package/src/auth/api-key-provider-module.ts +0 -213
- package/src/auth/base-provider-module.ts +0 -201
- package/src/auth/bedrock/provider-module.ts +0 -110
- package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
- package/src/auth/chatgpt/device-code-client.ts +0 -218
- package/src/auth/chatgpt/index.ts +0 -1
- package/src/auth/claude/oauth-module.ts +0 -280
- package/src/auth/cli/token-service.ts +0 -249
- package/src/auth/external/client.ts +0 -560
- package/src/auth/external/device-code-client.ts +0 -235
- package/src/auth/mcp/config-service.ts +0 -420
- package/src/auth/mcp/proxy.ts +0 -1086
- package/src/auth/mcp/string-substitution.ts +0 -17
- package/src/auth/mcp/tool-cache.ts +0 -90
- package/src/auth/oauth/base-client.ts +0 -267
- package/src/auth/oauth/client.ts +0 -153
- package/src/auth/oauth/credentials.ts +0 -7
- package/src/auth/oauth/providers.ts +0 -69
- package/src/auth/oauth/state-store.ts +0 -150
- package/src/auth/oauth-templates.ts +0 -179
- package/src/auth/provider-catalog.ts +0 -220
- package/src/auth/provider-model-options.ts +0 -41
- package/src/auth/settings/agent-settings-store.ts +0 -565
- package/src/auth/settings/auth-profiles-manager.ts +0 -216
- package/src/auth/settings/index.ts +0 -12
- package/src/auth/settings/model-preference-store.ts +0 -52
- package/src/auth/settings/model-selection.ts +0 -135
- package/src/auth/settings/resolved-settings-view.ts +0 -298
- package/src/auth/settings/template-utils.ts +0 -44
- package/src/auth/settings/token-service.ts +0 -88
- package/src/auth/system-env-store.ts +0 -98
- package/src/auth/user-agents-store.ts +0 -68
- package/src/channels/binding-service.ts +0 -214
- package/src/channels/index.ts +0 -4
- package/src/cli/gateway.ts +0 -1312
- package/src/cli/index.ts +0 -74
- package/src/commands/built-in-commands.ts +0 -80
- package/src/commands/command-dispatcher.ts +0 -94
- package/src/commands/command-reply-adapters.ts +0 -27
- package/src/config/file-loader.ts +0 -618
- package/src/config/index.ts +0 -588
- package/src/config/network-allowlist.ts +0 -71
- package/src/connections/chat-instance-manager.ts +0 -1284
- package/src/connections/chat-response-bridge.ts +0 -618
- package/src/connections/index.ts +0 -7
- package/src/connections/interaction-bridge.ts +0 -831
- package/src/connections/message-handler-bridge.ts +0 -440
- package/src/connections/platform-auth-methods.ts +0 -15
- package/src/connections/types.ts +0 -84
- package/src/gateway/connection-manager.ts +0 -291
- package/src/gateway/index.ts +0 -698
- package/src/gateway/job-router.ts +0 -201
- package/src/gateway-main.ts +0 -200
- package/src/index.ts +0 -41
- package/src/infrastructure/queue/index.ts +0 -12
- package/src/infrastructure/queue/queue-producer.ts +0 -148
- package/src/infrastructure/queue/redis-queue.ts +0 -361
- package/src/infrastructure/queue/types.ts +0 -133
- package/src/infrastructure/redis/system-message-limiter.ts +0 -94
- package/src/interactions/config-request-store.ts +0 -198
- package/src/interactions.ts +0 -363
- package/src/lobu.ts +0 -311
- package/src/metrics/prometheus.ts +0 -159
- package/src/modules/module-system.ts +0 -179
- package/src/orchestration/base-deployment-manager.ts +0 -900
- package/src/orchestration/deployment-utils.ts +0 -98
- package/src/orchestration/impl/docker-deployment.ts +0 -620
- package/src/orchestration/impl/embedded-deployment.ts +0 -268
- package/src/orchestration/impl/index.ts +0 -8
- package/src/orchestration/impl/k8s/deployment.ts +0 -1061
- package/src/orchestration/impl/k8s/helpers.ts +0 -610
- package/src/orchestration/impl/k8s/index.ts +0 -1
- package/src/orchestration/index.ts +0 -333
- package/src/orchestration/message-consumer.ts +0 -584
- package/src/orchestration/scheduled-wakeup.ts +0 -704
- package/src/permissions/approval-policy.ts +0 -36
- package/src/permissions/grant-store.ts +0 -219
- package/src/platform/file-handler.ts +0 -66
- package/src/platform/link-buttons.ts +0 -57
- package/src/platform/renderer-utils.ts +0 -44
- package/src/platform/response-renderer.ts +0 -84
- package/src/platform/unified-thread-consumer.ts +0 -194
- package/src/platform.ts +0 -318
- package/src/proxy/http-proxy.ts +0 -752
- package/src/proxy/proxy-manager.ts +0 -81
- package/src/proxy/secret-proxy.ts +0 -402
- package/src/proxy/token-refresh-job.ts +0 -143
- package/src/routes/internal/audio.ts +0 -141
- package/src/routes/internal/device-auth.ts +0 -652
- package/src/routes/internal/files.ts +0 -226
- package/src/routes/internal/history.ts +0 -69
- package/src/routes/internal/images.ts +0 -127
- package/src/routes/internal/interactions.ts +0 -84
- package/src/routes/internal/middleware.ts +0 -23
- package/src/routes/internal/schedule.ts +0 -226
- package/src/routes/internal/types.ts +0 -22
- package/src/routes/openapi-auto.ts +0 -239
- package/src/routes/public/agent-access.ts +0 -23
- package/src/routes/public/agent-config.ts +0 -675
- package/src/routes/public/agent-history.ts +0 -422
- package/src/routes/public/agent-schedules.ts +0 -296
- package/src/routes/public/agent.ts +0 -1086
- package/src/routes/public/agents.ts +0 -373
- package/src/routes/public/channels.ts +0 -191
- package/src/routes/public/cli-auth.ts +0 -896
- package/src/routes/public/connections.ts +0 -574
- package/src/routes/public/landing.ts +0 -16
- package/src/routes/public/oauth.ts +0 -147
- package/src/routes/public/settings-auth.ts +0 -104
- package/src/routes/public/slack.ts +0 -173
- package/src/routes/shared/agent-ownership.ts +0 -101
- package/src/routes/shared/token-verifier.ts +0 -34
- package/src/services/bedrock-model-catalog.ts +0 -217
- package/src/services/bedrock-openai-service.ts +0 -658
- package/src/services/core-services.ts +0 -1072
- package/src/services/image-generation-service.ts +0 -257
- package/src/services/instruction-service.ts +0 -318
- package/src/services/mcp-registry.ts +0 -94
- package/src/services/platform-helpers.ts +0 -287
- package/src/services/session-manager.ts +0 -262
- package/src/services/settings-resolver.ts +0 -74
- package/src/services/system-config-resolver.ts +0 -89
- package/src/services/system-skills-service.ts +0 -229
- package/src/services/transcription-service.ts +0 -684
- package/src/session.ts +0 -110
- package/src/spaces/index.ts +0 -1
- package/src/spaces/space-resolver.ts +0 -17
- package/src/stores/in-memory-agent-store.ts +0 -403
- package/src/stores/redis-agent-store.ts +0 -279
- package/src/utils/public-url.ts +0 -44
- package/src/utils/rate-limiter.ts +0 -94
- package/tsconfig.json +0 -33
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth Utility Routes
|
|
3
|
-
*
|
|
4
|
-
* OAuth code exchange and redirect handling.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
|
8
|
-
import type { ProviderOAuthStateStore } from "../../auth/oauth/state-store";
|
|
9
|
-
import { verifySettingsSessionOrToken } from "./settings-auth";
|
|
10
|
-
|
|
11
|
-
const TAG = "Auth";
|
|
12
|
-
const SuccessResponse = z.object({ success: z.boolean() });
|
|
13
|
-
const ErrorResponse = z.object({ error: z.string() });
|
|
14
|
-
|
|
15
|
-
export interface ProviderCredentialStore {
|
|
16
|
-
hasCredentials(agentId: string): Promise<boolean>;
|
|
17
|
-
deleteCredentials(agentId: string): Promise<void>;
|
|
18
|
-
setCredentials(agentId: string, credentials: unknown): Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ProviderOAuthClient {
|
|
22
|
-
generateCodeVerifier(): string;
|
|
23
|
-
buildAuthUrl(state: string, codeVerifier: string): string;
|
|
24
|
-
exchangeCodeForToken(
|
|
25
|
-
code: string,
|
|
26
|
-
codeVerifier: string,
|
|
27
|
-
redirectUri?: string,
|
|
28
|
-
state?: string
|
|
29
|
-
): Promise<unknown>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const codeExchangeRoute = createRoute({
|
|
33
|
-
method: "post",
|
|
34
|
-
path: "/{provider}/code",
|
|
35
|
-
tags: [TAG],
|
|
36
|
-
summary: "Exchange OAuth code for token",
|
|
37
|
-
request: {
|
|
38
|
-
query: z.object({ token: z.string().optional() }),
|
|
39
|
-
params: z.object({ provider: z.string() }),
|
|
40
|
-
body: {
|
|
41
|
-
content: {
|
|
42
|
-
"application/json": {
|
|
43
|
-
schema: z.object({ code: z.string() }),
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
responses: {
|
|
49
|
-
200: {
|
|
50
|
-
description: "Exchanged",
|
|
51
|
-
content: { "application/json": { schema: SuccessResponse } },
|
|
52
|
-
},
|
|
53
|
-
400: {
|
|
54
|
-
description: "Invalid",
|
|
55
|
-
content: { "application/json": { schema: ErrorResponse } },
|
|
56
|
-
},
|
|
57
|
-
401: {
|
|
58
|
-
description: "Unauthorized",
|
|
59
|
-
content: { "application/json": { schema: ErrorResponse } },
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export interface OAuthRoutesConfig {
|
|
65
|
-
providerStores?: Record<string, ProviderCredentialStore>;
|
|
66
|
-
oauthClients?: Record<string, ProviderOAuthClient>;
|
|
67
|
-
oauthStateStore?: ProviderOAuthStateStore;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function createOAuthRoutes(config: OAuthRoutesConfig): OpenAPIHono {
|
|
71
|
-
const app = new OpenAPIHono();
|
|
72
|
-
|
|
73
|
-
// --- Provider login redirect (excluded from docs) ---
|
|
74
|
-
app.get("/:provider/login", async (c) => {
|
|
75
|
-
const session = verifySettingsSessionOrToken(c);
|
|
76
|
-
const agentId = session?.agentId || c.req.query("agentId");
|
|
77
|
-
|
|
78
|
-
if (!agentId) return c.json({ error: "Missing agentId" }, 400);
|
|
79
|
-
|
|
80
|
-
if (!session) {
|
|
81
|
-
// No session — redirect through settings OAuth to establish one, then return here
|
|
82
|
-
const returnUrl = `${c.req.path}?agentId=${encodeURIComponent(agentId)}`;
|
|
83
|
-
return c.redirect(
|
|
84
|
-
`/connect/oauth/login?returnUrl=${encodeURIComponent(returnUrl)}`
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const oauthClient = config.oauthClients?.[c.req.param("provider")];
|
|
89
|
-
if (!oauthClient) return c.json({ error: "Unknown provider" }, 404);
|
|
90
|
-
if (!config.oauthStateStore)
|
|
91
|
-
return c.json({ error: "Not configured" }, 500);
|
|
92
|
-
|
|
93
|
-
const codeVerifier = oauthClient.generateCodeVerifier();
|
|
94
|
-
const state = await config.oauthStateStore.create({
|
|
95
|
-
userId: session.userId,
|
|
96
|
-
agentId,
|
|
97
|
-
codeVerifier,
|
|
98
|
-
context: { platform: session.platform, channelId: agentId },
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return c.redirect(oauthClient.buildAuthUrl(state, codeVerifier));
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// --- Provider code exchange ---
|
|
105
|
-
app.openapi(codeExchangeRoute, async (c): Promise<any> => {
|
|
106
|
-
const session = verifySettingsSessionOrToken(c);
|
|
107
|
-
const agentId = session?.agentId;
|
|
108
|
-
if (!session || !agentId) return c.json({ error: "Unauthorized" }, 401);
|
|
109
|
-
|
|
110
|
-
const { provider } = c.req.valid("param");
|
|
111
|
-
const oauthClient = config.oauthClients?.[provider];
|
|
112
|
-
const credentialStore = config.providerStores?.[provider];
|
|
113
|
-
if (!oauthClient || !credentialStore)
|
|
114
|
-
return c.json({ error: "Unknown provider" }, 404);
|
|
115
|
-
if (!config.oauthStateStore)
|
|
116
|
-
return c.json({ error: "Not configured" }, 500);
|
|
117
|
-
|
|
118
|
-
const { code: input } = c.req.valid("json");
|
|
119
|
-
const parts = input.split("#");
|
|
120
|
-
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
121
|
-
return c.json({ error: "Invalid format" }, 400);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const authCode = parts[0].trim();
|
|
125
|
-
const state = parts[1].trim();
|
|
126
|
-
const stateData = await config.oauthStateStore.consume(state);
|
|
127
|
-
if (!stateData) return c.json({ error: "Invalid state" }, 400);
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const credentials = await oauthClient.exchangeCodeForToken(
|
|
131
|
-
authCode,
|
|
132
|
-
stateData.codeVerifier,
|
|
133
|
-
"https://console.anthropic.com/oauth/code/callback",
|
|
134
|
-
state
|
|
135
|
-
);
|
|
136
|
-
await credentialStore.setCredentials(agentId, credentials);
|
|
137
|
-
return c.json({ success: true });
|
|
138
|
-
} catch (e) {
|
|
139
|
-
return c.json(
|
|
140
|
-
{ error: e instanceof Error ? e.message : "Exchange failed" },
|
|
141
|
-
400
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
return app;
|
|
147
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { decrypt, encrypt } from "@lobu/core";
|
|
2
|
-
import type { Context } from "hono";
|
|
3
|
-
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
4
|
-
import type { SettingsTokenPayload } from "../../auth/settings/token-service";
|
|
5
|
-
|
|
6
|
-
export type AuthProvider = (c: Context) => SettingsTokenPayload | null;
|
|
7
|
-
|
|
8
|
-
export const SETTINGS_SESSION_COOKIE_NAME = "lobu_settings_session";
|
|
9
|
-
|
|
10
|
-
let _authProvider: AuthProvider | null = null;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Set a custom auth provider for embedded mode.
|
|
14
|
-
* When set, verifySettingsSession delegates to this provider first,
|
|
15
|
-
* falling back to cookie auth only if it returns null.
|
|
16
|
-
*/
|
|
17
|
-
export function setAuthProvider(provider: AuthProvider | null): void {
|
|
18
|
-
_authProvider = provider;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function decodeSettingsPayload(
|
|
22
|
-
token: string | null | undefined
|
|
23
|
-
): SettingsTokenPayload | null {
|
|
24
|
-
if (!token || token.trim().length === 0) return null;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const decrypted = decrypt(token);
|
|
28
|
-
const payload = JSON.parse(decrypted) as SettingsTokenPayload;
|
|
29
|
-
|
|
30
|
-
if (!payload.userId || !payload.exp) return null;
|
|
31
|
-
if (Date.now() > payload.exp) return null;
|
|
32
|
-
|
|
33
|
-
return payload;
|
|
34
|
-
} catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isSecureRequest(c: Context): boolean {
|
|
40
|
-
const forwardedProto = c.req.header("x-forwarded-proto");
|
|
41
|
-
if (forwardedProto) {
|
|
42
|
-
return forwardedProto.split(",")[0]?.trim().toLowerCase() === "https";
|
|
43
|
-
}
|
|
44
|
-
return new URL(c.req.url).protocol === "https:";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Verify settings session.
|
|
49
|
-
* Checks injected auth provider first (for embedded mode),
|
|
50
|
-
* then falls back to cookie-based session auth.
|
|
51
|
-
*/
|
|
52
|
-
export function verifySettingsSession(c: Context): SettingsTokenPayload | null {
|
|
53
|
-
if (_authProvider) {
|
|
54
|
-
const result = _authProvider(c);
|
|
55
|
-
if (result) return result;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const token = getCookie(c, SETTINGS_SESSION_COOKIE_NAME);
|
|
59
|
-
return decodeSettingsPayload(token);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function verifySettingsToken(
|
|
63
|
-
token: string | null | undefined
|
|
64
|
-
): SettingsTokenPayload | null {
|
|
65
|
-
if (!token) return null;
|
|
66
|
-
return decodeSettingsPayload(token);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Resolve settings auth from an injected auth provider, cookie session,
|
|
71
|
-
* or a direct encrypted query token.
|
|
72
|
-
*/
|
|
73
|
-
export function verifySettingsSessionOrToken(
|
|
74
|
-
c: Context,
|
|
75
|
-
queryKey = "token"
|
|
76
|
-
): SettingsTokenPayload | null {
|
|
77
|
-
return verifySettingsSession(c) ?? verifySettingsToken(c.req.query(queryKey));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Set a settings session cookie from a SettingsTokenPayload.
|
|
82
|
-
*/
|
|
83
|
-
export function setSettingsSessionCookie(
|
|
84
|
-
c: Context,
|
|
85
|
-
session: SettingsTokenPayload
|
|
86
|
-
): void {
|
|
87
|
-
const token = encrypt(JSON.stringify(session));
|
|
88
|
-
const maxAgeSeconds = Math.max(
|
|
89
|
-
1,
|
|
90
|
-
Math.floor((session.exp - Date.now()) / 1000)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
setCookie(c, SETTINGS_SESSION_COOKIE_NAME, token, {
|
|
94
|
-
path: "/",
|
|
95
|
-
httpOnly: true,
|
|
96
|
-
sameSite: "Lax",
|
|
97
|
-
secure: isSecureRequest(c),
|
|
98
|
-
maxAge: maxAgeSeconds,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function clearSettingsSessionCookie(c: Context): void {
|
|
103
|
-
deleteCookie(c, SETTINGS_SESSION_COOKIE_NAME, { path: "/" });
|
|
104
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { createLogger } from "@lobu/core";
|
|
3
|
-
import { Hono } from "hono";
|
|
4
|
-
import { createSlackInstallStateStore } from "../../auth/oauth/state-store";
|
|
5
|
-
import {
|
|
6
|
-
renderOAuthErrorPage,
|
|
7
|
-
renderOAuthSuccessPage,
|
|
8
|
-
} from "../../auth/oauth-templates";
|
|
9
|
-
import type { ChatInstanceManager } from "../../connections/chat-instance-manager";
|
|
10
|
-
import { resolvePublicUrl } from "../../utils/public-url";
|
|
11
|
-
|
|
12
|
-
const logger = createLogger("slack-routes");
|
|
13
|
-
|
|
14
|
-
const DEFAULT_SLACK_BOT_SCOPES = [
|
|
15
|
-
"app_mentions:read",
|
|
16
|
-
"assistant:write",
|
|
17
|
-
"channels:history",
|
|
18
|
-
"channels:read",
|
|
19
|
-
"chat:write",
|
|
20
|
-
"chat:write.public",
|
|
21
|
-
"commands",
|
|
22
|
-
"files:read",
|
|
23
|
-
"files:write",
|
|
24
|
-
"groups:history",
|
|
25
|
-
"groups:read",
|
|
26
|
-
"im:history",
|
|
27
|
-
"im:read",
|
|
28
|
-
"im:write",
|
|
29
|
-
"mpim:read",
|
|
30
|
-
"reactions:read",
|
|
31
|
-
"reactions:write",
|
|
32
|
-
"users:read",
|
|
33
|
-
];
|
|
34
|
-
type SlackManifest = {
|
|
35
|
-
oauth_config?: {
|
|
36
|
-
scopes?: {
|
|
37
|
-
bot?: string[];
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function splitScopes(scopes: string): string[] {
|
|
43
|
-
return scopes
|
|
44
|
-
.split(",")
|
|
45
|
-
.map((scope) => scope.trim())
|
|
46
|
-
.filter(Boolean);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function loadSlackBotScopes(): Promise<string[]> {
|
|
50
|
-
const envScopes =
|
|
51
|
-
process.env.SLACK_OAUTH_SCOPES || process.env.SLACK_BOT_SCOPES;
|
|
52
|
-
if (envScopes) {
|
|
53
|
-
return splitScopes(envScopes);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const manifestPath =
|
|
57
|
-
process.env.SLACK_MANIFEST_PATH ||
|
|
58
|
-
"config/slack-app-manifest.community.json";
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const raw = await readFile(manifestPath, "utf8");
|
|
62
|
-
const manifest = JSON.parse(raw) as SlackManifest;
|
|
63
|
-
const scopes = manifest.oauth_config?.scopes?.bot;
|
|
64
|
-
if (Array.isArray(scopes) && scopes.length > 0) {
|
|
65
|
-
return scopes;
|
|
66
|
-
}
|
|
67
|
-
} catch (error) {
|
|
68
|
-
logger.warn(
|
|
69
|
-
{ manifestPath, error: String(error) },
|
|
70
|
-
"Failed to load Slack scopes from manifest, using defaults"
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return DEFAULT_SLACK_BOT_SCOPES;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function createSlackRoutes(manager: ChatInstanceManager): Hono {
|
|
78
|
-
const router = new Hono();
|
|
79
|
-
|
|
80
|
-
router.get("/slack/install", async (c) => {
|
|
81
|
-
const clientId = process.env.SLACK_CLIENT_ID;
|
|
82
|
-
if (!clientId) {
|
|
83
|
-
return c.html(
|
|
84
|
-
renderOAuthErrorPage(
|
|
85
|
-
"slack_not_configured",
|
|
86
|
-
"Slack OAuth is not configured on this gateway. Set SLACK_CLIENT_ID and try again."
|
|
87
|
-
),
|
|
88
|
-
503
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const redis = manager.getServices().getQueue().getRedisClient();
|
|
93
|
-
const stateStore = createSlackInstallStateStore(redis);
|
|
94
|
-
const redirectUri = resolvePublicUrl("/slack/oauth_callback", {
|
|
95
|
-
configuredUrl: manager.getServices().getPublicGatewayUrl?.(),
|
|
96
|
-
requestUrl: c.req.url,
|
|
97
|
-
});
|
|
98
|
-
const scopes = await loadSlackBotScopes();
|
|
99
|
-
const state = await stateStore.create({ redirectUri });
|
|
100
|
-
|
|
101
|
-
const oauthUrl = new URL("https://slack.com/oauth/v2/authorize");
|
|
102
|
-
oauthUrl.searchParams.set("client_id", clientId);
|
|
103
|
-
oauthUrl.searchParams.set("scope", scopes.join(","));
|
|
104
|
-
oauthUrl.searchParams.set("redirect_uri", redirectUri);
|
|
105
|
-
oauthUrl.searchParams.set("state", state);
|
|
106
|
-
|
|
107
|
-
return c.redirect(oauthUrl.toString(), 302);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
router.get("/slack/oauth_callback", async (c) => {
|
|
111
|
-
const state = c.req.query("state");
|
|
112
|
-
const code = c.req.query("code");
|
|
113
|
-
if (!state || !code) {
|
|
114
|
-
return c.html(
|
|
115
|
-
renderOAuthErrorPage(
|
|
116
|
-
"invalid_request",
|
|
117
|
-
"The Slack OAuth callback is missing the required state or code parameter."
|
|
118
|
-
),
|
|
119
|
-
400
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const redis = manager.getServices().getQueue().getRedisClient();
|
|
124
|
-
const stateStore = createSlackInstallStateStore(redis);
|
|
125
|
-
const oauthState = await stateStore.consume(state);
|
|
126
|
-
|
|
127
|
-
if (!oauthState) {
|
|
128
|
-
return c.html(
|
|
129
|
-
renderOAuthErrorPage(
|
|
130
|
-
"invalid_state",
|
|
131
|
-
"This Slack install link is invalid or has expired."
|
|
132
|
-
),
|
|
133
|
-
400
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const result = await manager.completeSlackOAuthInstall(
|
|
139
|
-
c.req.raw,
|
|
140
|
-
oauthState.redirectUri
|
|
141
|
-
);
|
|
142
|
-
return c.html(
|
|
143
|
-
renderOAuthSuccessPage(result.teamName || result.teamId, undefined, {
|
|
144
|
-
title: "Slack installed",
|
|
145
|
-
description: "Workspace connected to Lobu:",
|
|
146
|
-
details: `Connection ID: ${result.connectionId}`,
|
|
147
|
-
})
|
|
148
|
-
);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
logger.error({ error: String(error) }, "Slack OAuth callback failed");
|
|
151
|
-
return c.html(
|
|
152
|
-
renderOAuthErrorPage(
|
|
153
|
-
"slack_install_failed",
|
|
154
|
-
error instanceof Error
|
|
155
|
-
? error.message
|
|
156
|
-
: "Slack OAuth callback failed."
|
|
157
|
-
),
|
|
158
|
-
500
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
router.post("/slack/events", async (c) => {
|
|
164
|
-
try {
|
|
165
|
-
return await manager.handleSlackAppWebhook(c.req.raw);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
logger.error({ error: String(error) }, "Slack event handling failed");
|
|
168
|
-
return c.text("Slack webhook processing failed", 500);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
return router;
|
|
173
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import type { AgentConfigStore } from "@lobu/core";
|
|
2
|
-
import type { SettingsTokenPayload } from "../../auth/settings/token-service";
|
|
3
|
-
import type { UserAgentsStore } from "../../auth/user-agents-store";
|
|
4
|
-
import { getAuthMethod } from "../../connections/platform-auth-methods";
|
|
5
|
-
|
|
6
|
-
export interface AgentOwnershipConfig {
|
|
7
|
-
userAgentsStore?: UserAgentsStore;
|
|
8
|
-
agentMetadataStore?: Pick<AgentConfigStore, "getMetadata">;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface AgentOwnershipResult {
|
|
12
|
-
authorized: boolean;
|
|
13
|
-
ownerPlatform?: string;
|
|
14
|
-
ownerUserId?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function resolveSettingsLookupUserId(
|
|
18
|
-
session: SettingsTokenPayload
|
|
19
|
-
): string {
|
|
20
|
-
if (session.platform === "external") {
|
|
21
|
-
return session.oauthUserId || session.userId;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const isDeterministic = getAuthMethod(session.platform).type !== "oauth";
|
|
25
|
-
return isDeterministic
|
|
26
|
-
? session.userId
|
|
27
|
-
: session.oauthUserId || session.userId;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function sessionMatchesMetadataOwner(
|
|
31
|
-
session: SettingsTokenPayload,
|
|
32
|
-
ownerPlatform: string,
|
|
33
|
-
ownerUserId: string
|
|
34
|
-
): boolean {
|
|
35
|
-
const lookupUserId = resolveSettingsLookupUserId(session);
|
|
36
|
-
if (!lookupUserId || ownerUserId !== lookupUserId) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return ownerPlatform === session.platform || session.platform === "external";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function verifyOwnedAgentAccess(
|
|
44
|
-
session: SettingsTokenPayload,
|
|
45
|
-
agentId: string,
|
|
46
|
-
config: AgentOwnershipConfig
|
|
47
|
-
): Promise<AgentOwnershipResult> {
|
|
48
|
-
if (session.isAdmin) {
|
|
49
|
-
return { authorized: true };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (session.agentId) {
|
|
53
|
-
return { authorized: session.agentId === agentId };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const lookupUserId = resolveSettingsLookupUserId(session);
|
|
57
|
-
if (config.userAgentsStore) {
|
|
58
|
-
const owns = await config.userAgentsStore.ownsAgent(
|
|
59
|
-
session.platform,
|
|
60
|
-
lookupUserId,
|
|
61
|
-
agentId
|
|
62
|
-
);
|
|
63
|
-
if (owns) {
|
|
64
|
-
return {
|
|
65
|
-
authorized: true,
|
|
66
|
-
ownerPlatform: session.platform,
|
|
67
|
-
ownerUserId: lookupUserId,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!config.agentMetadataStore) {
|
|
73
|
-
return { authorized: false };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const metadata = await config.agentMetadataStore.getMetadata(agentId);
|
|
77
|
-
if (
|
|
78
|
-
!metadata?.owner ||
|
|
79
|
-
!sessionMatchesMetadataOwner(
|
|
80
|
-
session,
|
|
81
|
-
metadata.owner.platform,
|
|
82
|
-
metadata.owner.userId
|
|
83
|
-
)
|
|
84
|
-
) {
|
|
85
|
-
return { authorized: false };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (config.userAgentsStore) {
|
|
89
|
-
config.userAgentsStore
|
|
90
|
-
.addAgent(session.platform, lookupUserId, agentId)
|
|
91
|
-
.catch(() => {
|
|
92
|
-
/* best-effort reconciliation */
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
authorized: true,
|
|
98
|
-
ownerPlatform: metadata.owner.platform,
|
|
99
|
-
ownerUserId: metadata.owner.userId,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared token verification utility for public routes.
|
|
3
|
-
*
|
|
4
|
-
* Verifies a settings token against an agentId by checking direct agentId match,
|
|
5
|
-
* user ownership via UserAgentsStore, or canonical metadata owner fallback.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { AgentConfigStore } from "@lobu/core";
|
|
9
|
-
import type { SettingsTokenPayload } from "../../auth/settings/token-service";
|
|
10
|
-
import type { UserAgentsStore } from "../../auth/user-agents-store";
|
|
11
|
-
import { verifyOwnedAgentAccess } from "./agent-ownership";
|
|
12
|
-
|
|
13
|
-
export interface TokenVerifierConfig {
|
|
14
|
-
userAgentsStore?: UserAgentsStore;
|
|
15
|
-
agentMetadataStore?: Pick<AgentConfigStore, "getMetadata">;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Create a token verifier function scoped to a given config.
|
|
20
|
-
*
|
|
21
|
-
* The returned async function accepts a decoded settings token payload and an
|
|
22
|
-
* agentId, then returns the payload if the caller is authorised, or null.
|
|
23
|
-
*/
|
|
24
|
-
export function createTokenVerifier(config: TokenVerifierConfig) {
|
|
25
|
-
return async (
|
|
26
|
-
payload: SettingsTokenPayload | null,
|
|
27
|
-
agentId: string
|
|
28
|
-
): Promise<SettingsTokenPayload | null> => {
|
|
29
|
-
if (!payload) return null;
|
|
30
|
-
|
|
31
|
-
const result = await verifyOwnedAgentAccess(payload, agentId, config);
|
|
32
|
-
return result.authorized ? payload : null;
|
|
33
|
-
};
|
|
34
|
-
}
|