@lobu/gateway 2.8.0 → 3.0.6

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.
Files changed (175) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/agent-config-routes.test.ts +254 -0
  3. package/src/__tests__/agent-history-routes.test.ts +72 -0
  4. package/src/__tests__/agent-routes.test.ts +68 -0
  5. package/src/__tests__/agent-schedules-routes.test.ts +59 -0
  6. package/src/__tests__/agent-settings-store.test.ts +323 -0
  7. package/src/__tests__/chat-instance-manager-slack.test.ts +204 -0
  8. package/src/__tests__/chat-response-bridge.test.ts +131 -0
  9. package/src/__tests__/config-memory-plugins.test.ts +92 -0
  10. package/src/__tests__/config-request-store.test.ts +127 -0
  11. package/src/__tests__/connection-routes.test.ts +144 -0
  12. package/src/__tests__/core-services-store-selection.test.ts +92 -0
  13. package/src/__tests__/docker-deployment.test.ts +1211 -0
  14. package/src/__tests__/embedded-deployment.test.ts +342 -0
  15. package/src/__tests__/grant-store.test.ts +148 -0
  16. package/src/__tests__/http-proxy.test.ts +281 -0
  17. package/src/__tests__/instruction-service.test.ts +37 -0
  18. package/src/__tests__/link-buttons.test.ts +112 -0
  19. package/src/__tests__/lobu.test.ts +32 -0
  20. package/src/__tests__/mcp-config-service.test.ts +347 -0
  21. package/src/__tests__/mcp-proxy.test.ts +696 -0
  22. package/src/__tests__/message-handler-bridge.test.ts +17 -0
  23. package/src/__tests__/model-selection.test.ts +172 -0
  24. package/src/__tests__/oauth-templates.test.ts +39 -0
  25. package/src/__tests__/platform-adapter-slack-send.test.ts +114 -0
  26. package/src/__tests__/platform-helpers-model-resolution.test.ts +253 -0
  27. package/src/__tests__/provider-inheritance.test.ts +212 -0
  28. package/src/__tests__/routes/cli-auth.test.ts +337 -0
  29. package/src/__tests__/routes/interactions.test.ts +121 -0
  30. package/src/__tests__/secret-proxy.test.ts +85 -0
  31. package/src/__tests__/session-manager.test.ts +572 -0
  32. package/src/__tests__/setup.ts +133 -0
  33. package/src/__tests__/skill-and-mcp-registry.test.ts +203 -0
  34. package/src/__tests__/slack-routes.test.ts +161 -0
  35. package/src/__tests__/system-config-resolver.test.ts +75 -0
  36. package/src/__tests__/system-message-limiter.test.ts +89 -0
  37. package/src/__tests__/system-skills-service.test.ts +362 -0
  38. package/src/__tests__/transcription-service.test.ts +222 -0
  39. package/src/__tests__/utils/rate-limiter.test.ts +102 -0
  40. package/src/__tests__/worker-connection-manager.test.ts +497 -0
  41. package/src/__tests__/worker-job-router.test.ts +722 -0
  42. package/src/api/index.ts +1 -0
  43. package/src/api/platform.ts +292 -0
  44. package/src/api/response-renderer.ts +157 -0
  45. package/src/auth/agent-metadata-store.ts +168 -0
  46. package/src/auth/api-auth-middleware.ts +69 -0
  47. package/src/auth/api-key-provider-module.ts +213 -0
  48. package/src/auth/base-provider-module.ts +201 -0
  49. package/src/auth/chatgpt/chatgpt-oauth-module.ts +185 -0
  50. package/src/auth/chatgpt/device-code-client.ts +218 -0
  51. package/src/auth/chatgpt/index.ts +1 -0
  52. package/src/auth/claude/oauth-module.ts +280 -0
  53. package/src/auth/cli/token-service.ts +249 -0
  54. package/src/auth/external/client.ts +560 -0
  55. package/src/auth/external/device-code-client.ts +225 -0
  56. package/src/auth/mcp/config-service.ts +392 -0
  57. package/src/auth/mcp/proxy.ts +1088 -0
  58. package/src/auth/mcp/string-substitution.ts +17 -0
  59. package/src/auth/mcp/tool-cache.ts +90 -0
  60. package/src/auth/oauth/base-client.ts +267 -0
  61. package/src/auth/oauth/client.ts +153 -0
  62. package/src/auth/oauth/credentials.ts +7 -0
  63. package/src/auth/oauth/providers.ts +69 -0
  64. package/src/auth/oauth/state-store.ts +150 -0
  65. package/src/auth/oauth-templates.ts +179 -0
  66. package/src/auth/provider-catalog.ts +220 -0
  67. package/src/auth/provider-model-options.ts +41 -0
  68. package/src/auth/settings/agent-settings-store.ts +565 -0
  69. package/src/auth/settings/auth-profiles-manager.ts +216 -0
  70. package/src/auth/settings/index.ts +12 -0
  71. package/src/auth/settings/model-preference-store.ts +52 -0
  72. package/src/auth/settings/model-selection.ts +135 -0
  73. package/src/auth/settings/resolved-settings-view.ts +298 -0
  74. package/src/auth/settings/template-utils.ts +44 -0
  75. package/src/auth/settings/token-service.ts +88 -0
  76. package/src/auth/system-env-store.ts +98 -0
  77. package/src/auth/user-agents-store.ts +68 -0
  78. package/src/channels/binding-service.ts +214 -0
  79. package/src/channels/index.ts +4 -0
  80. package/src/cli/gateway.ts +1304 -0
  81. package/src/cli/index.ts +74 -0
  82. package/src/commands/built-in-commands.ts +80 -0
  83. package/src/commands/command-dispatcher.ts +94 -0
  84. package/src/commands/command-reply-adapters.ts +27 -0
  85. package/src/config/file-loader.ts +618 -0
  86. package/src/config/index.ts +588 -0
  87. package/src/config/network-allowlist.ts +71 -0
  88. package/src/connections/chat-instance-manager.ts +1284 -0
  89. package/src/connections/chat-response-bridge.ts +618 -0
  90. package/src/connections/index.ts +7 -0
  91. package/src/connections/interaction-bridge.ts +831 -0
  92. package/src/connections/message-handler-bridge.ts +415 -0
  93. package/src/connections/platform-auth-methods.ts +15 -0
  94. package/src/connections/types.ts +84 -0
  95. package/src/gateway/connection-manager.ts +291 -0
  96. package/src/gateway/index.ts +700 -0
  97. package/src/gateway/job-router.ts +201 -0
  98. package/src/gateway-main.ts +200 -0
  99. package/src/index.ts +41 -0
  100. package/src/infrastructure/queue/index.ts +12 -0
  101. package/src/infrastructure/queue/queue-producer.ts +148 -0
  102. package/src/infrastructure/queue/redis-queue.ts +361 -0
  103. package/src/infrastructure/queue/types.ts +133 -0
  104. package/src/infrastructure/redis/system-message-limiter.ts +94 -0
  105. package/src/interactions/config-request-store.ts +198 -0
  106. package/src/interactions.ts +363 -0
  107. package/src/lobu.ts +311 -0
  108. package/src/metrics/prometheus.ts +159 -0
  109. package/src/modules/module-system.ts +179 -0
  110. package/src/orchestration/base-deployment-manager.ts +900 -0
  111. package/src/orchestration/deployment-utils.ts +98 -0
  112. package/src/orchestration/impl/docker-deployment.ts +620 -0
  113. package/src/orchestration/impl/embedded-deployment.ts +268 -0
  114. package/src/orchestration/impl/index.ts +8 -0
  115. package/src/orchestration/impl/k8s/deployment.ts +1061 -0
  116. package/src/orchestration/impl/k8s/helpers.ts +610 -0
  117. package/src/orchestration/impl/k8s/index.ts +1 -0
  118. package/src/orchestration/index.ts +333 -0
  119. package/src/orchestration/message-consumer.ts +584 -0
  120. package/src/orchestration/scheduled-wakeup.ts +704 -0
  121. package/src/permissions/approval-policy.ts +36 -0
  122. package/src/permissions/grant-store.ts +219 -0
  123. package/src/platform/file-handler.ts +66 -0
  124. package/src/platform/link-buttons.ts +57 -0
  125. package/src/platform/renderer-utils.ts +44 -0
  126. package/src/platform/response-renderer.ts +84 -0
  127. package/src/platform/unified-thread-consumer.ts +187 -0
  128. package/src/platform.ts +318 -0
  129. package/src/proxy/http-proxy.ts +752 -0
  130. package/src/proxy/proxy-manager.ts +81 -0
  131. package/src/proxy/secret-proxy.ts +402 -0
  132. package/src/proxy/token-refresh-job.ts +143 -0
  133. package/src/routes/internal/audio.ts +141 -0
  134. package/src/routes/internal/device-auth.ts +566 -0
  135. package/src/routes/internal/files.ts +226 -0
  136. package/src/routes/internal/history.ts +69 -0
  137. package/src/routes/internal/images.ts +127 -0
  138. package/src/routes/internal/interactions.ts +84 -0
  139. package/src/routes/internal/middleware.ts +23 -0
  140. package/src/routes/internal/schedule.ts +226 -0
  141. package/src/routes/internal/types.ts +22 -0
  142. package/src/routes/openapi-auto.ts +239 -0
  143. package/src/routes/public/agent-access.ts +23 -0
  144. package/src/routes/public/agent-config.ts +675 -0
  145. package/src/routes/public/agent-history.ts +422 -0
  146. package/src/routes/public/agent-schedules.ts +296 -0
  147. package/src/routes/public/agent.ts +1086 -0
  148. package/src/routes/public/agents.ts +373 -0
  149. package/src/routes/public/channels.ts +191 -0
  150. package/src/routes/public/cli-auth.ts +883 -0
  151. package/src/routes/public/connections.ts +574 -0
  152. package/src/routes/public/landing.ts +16 -0
  153. package/src/routes/public/oauth.ts +147 -0
  154. package/src/routes/public/settings-auth.ts +104 -0
  155. package/src/routes/public/slack.ts +173 -0
  156. package/src/routes/shared/agent-ownership.ts +101 -0
  157. package/src/routes/shared/token-verifier.ts +34 -0
  158. package/src/services/core-services.ts +1053 -0
  159. package/src/services/image-generation-service.ts +257 -0
  160. package/src/services/instruction-service.ts +318 -0
  161. package/src/services/mcp-registry.ts +94 -0
  162. package/src/services/platform-helpers.ts +287 -0
  163. package/src/services/session-manager.ts +262 -0
  164. package/src/services/settings-resolver.ts +74 -0
  165. package/src/services/system-config-resolver.ts +90 -0
  166. package/src/services/system-skills-service.ts +229 -0
  167. package/src/services/transcription-service.ts +684 -0
  168. package/src/session.ts +110 -0
  169. package/src/spaces/index.ts +1 -0
  170. package/src/spaces/space-resolver.ts +17 -0
  171. package/src/stores/in-memory-agent-store.ts +403 -0
  172. package/src/stores/redis-agent-store.ts +279 -0
  173. package/src/utils/public-url.ts +44 -0
  174. package/src/utils/rate-limiter.ts +94 -0
  175. package/tsconfig.json +33 -0
@@ -0,0 +1,249 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createLogger, decrypt, encrypt } from "@lobu/core";
3
+ import type Redis from "ioredis";
4
+
5
+ const logger = createLogger("cli-token-service");
6
+
7
+ const ACCESS_TOKEN_TTL_MS = 60 * 60 * 1000;
8
+ const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
9
+
10
+ interface CliAccessTokenPayload {
11
+ type: "cli-access";
12
+ sessionId: string;
13
+ userId: string;
14
+ email?: string;
15
+ name?: string;
16
+ exp: number;
17
+ iat: number;
18
+ }
19
+
20
+ interface CliRefreshTokenPayload {
21
+ type: "cli-refresh";
22
+ sessionId: string;
23
+ refreshTokenId: string;
24
+ exp: number;
25
+ iat: number;
26
+ }
27
+
28
+ interface CliSessionRecord {
29
+ sessionId: string;
30
+ userId: string;
31
+ email?: string;
32
+ name?: string;
33
+ refreshTokenId: string;
34
+ createdAt: number;
35
+ expiresAt: number;
36
+ }
37
+
38
+ export interface CliTokenIdentity {
39
+ userId: string;
40
+ email?: string;
41
+ name?: string;
42
+ }
43
+
44
+ export interface CliIssuedTokens {
45
+ accessToken: string;
46
+ refreshToken: string;
47
+ expiresAt: number;
48
+ user: CliTokenIdentity;
49
+ }
50
+
51
+ export interface CliAccessTokenIdentity extends CliTokenIdentity {
52
+ sessionId: string;
53
+ expiresAt: number;
54
+ }
55
+
56
+ export class CliTokenService {
57
+ constructor(private readonly redis: Redis) {}
58
+
59
+ async issueTokens(identity: CliTokenIdentity): Promise<CliIssuedTokens> {
60
+ const session = this.createSessionRecord(identity);
61
+ await this.saveSession(session);
62
+ return this.buildIssuedTokens(session);
63
+ }
64
+
65
+ async refreshTokens(refreshToken: string): Promise<CliIssuedTokens | null> {
66
+ const payload = this.parseRefreshToken(refreshToken);
67
+ if (!payload) {
68
+ return null;
69
+ }
70
+
71
+ const session = await this.getSession(payload.sessionId);
72
+ if (!session) {
73
+ logger.warn("CLI refresh rejected: session not found", {
74
+ sessionId: payload.sessionId,
75
+ });
76
+ return null;
77
+ }
78
+
79
+ if (session.refreshTokenId !== payload.refreshTokenId) {
80
+ logger.warn("CLI refresh rejected: token rotation mismatch", {
81
+ sessionId: payload.sessionId,
82
+ });
83
+ return null;
84
+ }
85
+
86
+ if (session.expiresAt <= Date.now()) {
87
+ logger.warn("CLI refresh rejected: session expired", {
88
+ sessionId: payload.sessionId,
89
+ });
90
+ await this.deleteSession(session.sessionId);
91
+ return null;
92
+ }
93
+
94
+ session.refreshTokenId = this.generateId();
95
+ await this.saveSession(session);
96
+ return this.buildIssuedTokens(session);
97
+ }
98
+
99
+ async verifyAccessToken(
100
+ accessToken: string
101
+ ): Promise<CliAccessTokenIdentity | null> {
102
+ const payload = this.parseAccessToken(accessToken);
103
+ if (!payload) {
104
+ return null;
105
+ }
106
+
107
+ const session = await this.getSession(payload.sessionId);
108
+ if (!session) {
109
+ logger.warn("CLI access token rejected: session not found", {
110
+ sessionId: payload.sessionId,
111
+ });
112
+ return null;
113
+ }
114
+
115
+ if (session.expiresAt <= Date.now()) {
116
+ logger.warn("CLI access token rejected: session expired", {
117
+ sessionId: payload.sessionId,
118
+ });
119
+ await this.deleteSession(session.sessionId);
120
+ return null;
121
+ }
122
+
123
+ return {
124
+ sessionId: session.sessionId,
125
+ userId: session.userId,
126
+ email: session.email,
127
+ name: session.name,
128
+ expiresAt: payload.exp,
129
+ };
130
+ }
131
+
132
+ async revokeSessionByRefreshToken(refreshToken: string): Promise<void> {
133
+ const payload = this.parseRefreshToken(refreshToken);
134
+ if (!payload) {
135
+ return;
136
+ }
137
+ await this.deleteSession(payload.sessionId);
138
+ }
139
+
140
+ private buildIssuedTokens(session: CliSessionRecord): CliIssuedTokens {
141
+ const accessPayload: CliAccessTokenPayload = {
142
+ type: "cli-access",
143
+ sessionId: session.sessionId,
144
+ userId: session.userId,
145
+ email: session.email,
146
+ name: session.name,
147
+ iat: Date.now(),
148
+ exp: Date.now() + ACCESS_TOKEN_TTL_MS,
149
+ };
150
+ const refreshPayload: CliRefreshTokenPayload = {
151
+ type: "cli-refresh",
152
+ sessionId: session.sessionId,
153
+ refreshTokenId: session.refreshTokenId,
154
+ iat: Date.now(),
155
+ exp: session.expiresAt,
156
+ };
157
+
158
+ return {
159
+ accessToken: encrypt(JSON.stringify(accessPayload)),
160
+ refreshToken: encrypt(JSON.stringify(refreshPayload)),
161
+ expiresAt: accessPayload.exp,
162
+ user: {
163
+ userId: session.userId,
164
+ email: session.email,
165
+ name: session.name,
166
+ },
167
+ };
168
+ }
169
+
170
+ private createSessionRecord(identity: CliTokenIdentity): CliSessionRecord {
171
+ const now = Date.now();
172
+ return {
173
+ sessionId: this.generateId(),
174
+ userId: identity.userId,
175
+ email: identity.email,
176
+ name: identity.name,
177
+ refreshTokenId: this.generateId(),
178
+ createdAt: now,
179
+ expiresAt: now + REFRESH_TOKEN_TTL_MS,
180
+ };
181
+ }
182
+
183
+ private async saveSession(session: CliSessionRecord): Promise<void> {
184
+ const ttlSeconds = Math.max(
185
+ 1,
186
+ Math.ceil((session.expiresAt - Date.now()) / 1000)
187
+ );
188
+ await this.redis.setex(
189
+ this.getSessionKey(session.sessionId),
190
+ ttlSeconds,
191
+ JSON.stringify(session)
192
+ );
193
+ }
194
+
195
+ private async getSession(
196
+ sessionId: string
197
+ ): Promise<CliSessionRecord | null> {
198
+ const raw = await this.redis.get(this.getSessionKey(sessionId));
199
+ if (!raw) {
200
+ return null;
201
+ }
202
+
203
+ try {
204
+ return JSON.parse(raw) as CliSessionRecord;
205
+ } catch (error) {
206
+ logger.error("Failed to parse CLI session", { sessionId, error });
207
+ await this.deleteSession(sessionId);
208
+ return null;
209
+ }
210
+ }
211
+
212
+ private async deleteSession(sessionId: string): Promise<void> {
213
+ await this.redis.del(this.getSessionKey(sessionId));
214
+ }
215
+
216
+ private parseAccessToken(token: string): CliAccessTokenPayload | null {
217
+ return this.parseToken<CliAccessTokenPayload>(token, "cli-access");
218
+ }
219
+
220
+ private parseRefreshToken(token: string): CliRefreshTokenPayload | null {
221
+ return this.parseToken<CliRefreshTokenPayload>(token, "cli-refresh");
222
+ }
223
+
224
+ private parseToken<T extends { type: string; exp: number }>(
225
+ token: string,
226
+ expectedType: string
227
+ ): T | null {
228
+ try {
229
+ const payload = JSON.parse(decrypt(token)) as T;
230
+ if (payload.type !== expectedType) {
231
+ return null;
232
+ }
233
+ if (typeof payload.exp !== "number" || payload.exp <= Date.now()) {
234
+ return null;
235
+ }
236
+ return payload;
237
+ } catch {
238
+ return null;
239
+ }
240
+ }
241
+
242
+ private getSessionKey(sessionId: string): string {
243
+ return `cli:auth:session:${sessionId}`;
244
+ }
245
+
246
+ private generateId(): string {
247
+ return randomBytes(24).toString("base64url");
248
+ }
249
+ }