@chaaskit/server 0.1.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.
Files changed (189) hide show
  1. package/dist/api/admin.js +438 -0
  2. package/dist/api/admin.js.map +1 -0
  3. package/dist/api/agents.js +21 -0
  4. package/dist/api/agents.js.map +1 -0
  5. package/dist/api/api-keys.js +122 -0
  6. package/dist/api/api-keys.js.map +1 -0
  7. package/dist/api/auth.js +399 -0
  8. package/dist/api/auth.js.map +1 -0
  9. package/dist/api/chat.js +900 -0
  10. package/dist/api/chat.js.map +1 -0
  11. package/dist/api/config.js +91 -0
  12. package/dist/api/config.js.map +1 -0
  13. package/dist/api/documents.js +237 -0
  14. package/dist/api/documents.js.map +1 -0
  15. package/dist/api/export.js +107 -0
  16. package/dist/api/export.js.map +1 -0
  17. package/dist/api/health.js +25 -0
  18. package/dist/api/health.js.map +1 -0
  19. package/dist/api/mcp-server.js +84 -0
  20. package/dist/api/mcp-server.js.map +1 -0
  21. package/dist/api/mcp.js +400 -0
  22. package/dist/api/mcp.js.map +1 -0
  23. package/dist/api/mentions.js +94 -0
  24. package/dist/api/mentions.js.map +1 -0
  25. package/dist/api/oauth.js +366 -0
  26. package/dist/api/oauth.js.map +1 -0
  27. package/dist/api/payments.js +473 -0
  28. package/dist/api/payments.js.map +1 -0
  29. package/dist/api/projects.js +301 -0
  30. package/dist/api/projects.js.map +1 -0
  31. package/dist/api/scheduled-prompts.js +617 -0
  32. package/dist/api/scheduled-prompts.js.map +1 -0
  33. package/dist/api/search.js +85 -0
  34. package/dist/api/search.js.map +1 -0
  35. package/dist/api/share.js +188 -0
  36. package/dist/api/share.js.map +1 -0
  37. package/dist/api/slack.js +468 -0
  38. package/dist/api/slack.js.map +1 -0
  39. package/dist/api/teams.js +693 -0
  40. package/dist/api/teams.js.map +1 -0
  41. package/dist/api/templates.js +134 -0
  42. package/dist/api/templates.js.map +1 -0
  43. package/dist/api/threads.js +323 -0
  44. package/dist/api/threads.js.map +1 -0
  45. package/dist/api/upload.js +57 -0
  46. package/dist/api/upload.js.map +1 -0
  47. package/dist/api/user.js +111 -0
  48. package/dist/api/user.js.map +1 -0
  49. package/dist/api/v1/openai.js +245 -0
  50. package/dist/api/v1/openai.js.map +1 -0
  51. package/dist/app.js +168 -0
  52. package/dist/app.js.map +1 -0
  53. package/dist/bin/cli.js +57 -0
  54. package/dist/bin/cli.js.map +1 -0
  55. package/dist/commands/db-sync.js +108 -0
  56. package/dist/commands/db-sync.js.map +1 -0
  57. package/dist/config/loader.js +374 -0
  58. package/dist/config/loader.js.map +1 -0
  59. package/dist/documents/extractors.js +136 -0
  60. package/dist/documents/extractors.js.map +1 -0
  61. package/dist/extensions/glob.js +53 -0
  62. package/dist/extensions/glob.js.map +1 -0
  63. package/dist/extensions/loader.js +72 -0
  64. package/dist/extensions/loader.js.map +1 -0
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/loaders/index.js +75 -0
  68. package/dist/loaders/index.js.map +1 -0
  69. package/dist/mcp/client.js +551 -0
  70. package/dist/mcp/client.js.map +1 -0
  71. package/dist/mcp/server.js +335 -0
  72. package/dist/mcp/server.js.map +1 -0
  73. package/dist/middleware/apiKeyAuth.js +136 -0
  74. package/dist/middleware/apiKeyAuth.js.map +1 -0
  75. package/dist/middleware/auth.js +192 -0
  76. package/dist/middleware/auth.js.map +1 -0
  77. package/dist/middleware/errorHandler.js +41 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/mcpServerAuth.js +164 -0
  80. package/dist/middleware/mcpServerAuth.js.map +1 -0
  81. package/dist/middleware/requestLogger.js +9 -0
  82. package/dist/middleware/requestLogger.js.map +1 -0
  83. package/dist/middleware/team.js +132 -0
  84. package/dist/middleware/team.js.map +1 -0
  85. package/dist/oauth/server.js +410 -0
  86. package/dist/oauth/server.js.map +1 -0
  87. package/dist/queue/cli.js +93 -0
  88. package/dist/queue/cli.js.map +1 -0
  89. package/dist/queue/handlers/index.js +91 -0
  90. package/dist/queue/handlers/index.js.map +1 -0
  91. package/dist/queue/handlers/scheduled-prompt.js +270 -0
  92. package/dist/queue/handlers/scheduled-prompt.js.map +1 -0
  93. package/dist/queue/index.js +91 -0
  94. package/dist/queue/index.js.map +1 -0
  95. package/dist/queue/providers/memory.js +296 -0
  96. package/dist/queue/providers/memory.js.map +1 -0
  97. package/dist/queue/providers/sqs.js +275 -0
  98. package/dist/queue/providers/sqs.js.map +1 -0
  99. package/dist/queue/scheduler.js +355 -0
  100. package/dist/queue/scheduler.js.map +1 -0
  101. package/dist/queue/types.js +5 -0
  102. package/dist/queue/types.js.map +1 -0
  103. package/dist/queue/worker.js +230 -0
  104. package/dist/queue/worker.js.map +1 -0
  105. package/dist/registry/index.js +40 -0
  106. package/dist/registry/index.js.map +1 -0
  107. package/dist/server.js +207 -0
  108. package/dist/server.js.map +1 -0
  109. package/dist/services/agent.js +530 -0
  110. package/dist/services/agent.js.map +1 -0
  111. package/dist/services/agents.js +194 -0
  112. package/dist/services/agents.js.map +1 -0
  113. package/dist/services/documents.js +507 -0
  114. package/dist/services/documents.js.map +1 -0
  115. package/dist/services/email/index.js +91 -0
  116. package/dist/services/email/index.js.map +1 -0
  117. package/dist/services/email/providers/ses.js +97 -0
  118. package/dist/services/email/providers/ses.js.map +1 -0
  119. package/dist/services/email/templates.js +194 -0
  120. package/dist/services/email/templates.js.map +1 -0
  121. package/dist/services/email/types.js +5 -0
  122. package/dist/services/email/types.js.map +1 -0
  123. package/dist/services/encryption.js +69 -0
  124. package/dist/services/encryption.js.map +1 -0
  125. package/dist/services/oauth-discovery.js +226 -0
  126. package/dist/services/oauth-discovery.js.map +1 -0
  127. package/dist/services/pendingConfirmation.js +105 -0
  128. package/dist/services/pendingConfirmation.js.map +1 -0
  129. package/dist/services/scheduledPrompts.js +70 -0
  130. package/dist/services/scheduledPrompts.js.map +1 -0
  131. package/dist/services/slack/client.js +174 -0
  132. package/dist/services/slack/client.js.map +1 -0
  133. package/dist/services/slack/events.js +189 -0
  134. package/dist/services/slack/events.js.map +1 -0
  135. package/dist/services/slack/index.js +6 -0
  136. package/dist/services/slack/index.js.map +1 -0
  137. package/dist/services/slack/notifications.js +124 -0
  138. package/dist/services/slack/notifications.js.map +1 -0
  139. package/dist/services/slack/signature.js +74 -0
  140. package/dist/services/slack/signature.js.map +1 -0
  141. package/dist/services/slack/thread-context.js +191 -0
  142. package/dist/services/slack/thread-context.js.map +1 -0
  143. package/dist/services/toolConfirmation.js +55 -0
  144. package/dist/services/toolConfirmation.js.map +1 -0
  145. package/dist/services/usage.js +241 -0
  146. package/dist/services/usage.js.map +1 -0
  147. package/dist/ssr/build.js +90 -0
  148. package/dist/ssr/build.js.map +1 -0
  149. package/dist/ssr/components/SSRMessageList.js +120 -0
  150. package/dist/ssr/components/SSRMessageList.js.map +1 -0
  151. package/dist/ssr/entry.client.js +8 -0
  152. package/dist/ssr/entry.client.js.map +1 -0
  153. package/dist/ssr/entry.server.js +71 -0
  154. package/dist/ssr/entry.server.js.map +1 -0
  155. package/dist/ssr/handler.js +51 -0
  156. package/dist/ssr/handler.js.map +1 -0
  157. package/dist/ssr/root.js +184 -0
  158. package/dist/ssr/root.js.map +1 -0
  159. package/dist/ssr/routes/login.js +140 -0
  160. package/dist/ssr/routes/login.js.map +1 -0
  161. package/dist/ssr/routes/pricing.js +195 -0
  162. package/dist/ssr/routes/pricing.js.map +1 -0
  163. package/dist/ssr/routes/privacy.js +39 -0
  164. package/dist/ssr/routes/privacy.js.map +1 -0
  165. package/dist/ssr/routes/register.js +148 -0
  166. package/dist/ssr/routes/register.js.map +1 -0
  167. package/dist/ssr/routes/shared.$shareId.js +153 -0
  168. package/dist/ssr/routes/shared.$shareId.js.map +1 -0
  169. package/dist/ssr/routes/terms.js +39 -0
  170. package/dist/ssr/routes/terms.js.map +1 -0
  171. package/dist/storage/index.js +43 -0
  172. package/dist/storage/index.js.map +1 -0
  173. package/dist/storage/providers/database.js +38 -0
  174. package/dist/storage/providers/database.js.map +1 -0
  175. package/dist/storage/providers/filesystem.js +51 -0
  176. package/dist/storage/providers/filesystem.js.map +1 -0
  177. package/dist/storage/types.js +2 -0
  178. package/dist/storage/types.js.map +1 -0
  179. package/dist/tools/documents.js +336 -0
  180. package/dist/tools/documents.js.map +1 -0
  181. package/dist/tools/get-plan-usage.js +82 -0
  182. package/dist/tools/get-plan-usage.js.map +1 -0
  183. package/dist/tools/index.js +106 -0
  184. package/dist/tools/index.js.map +1 -0
  185. package/dist/tools/types.js +2 -0
  186. package/dist/tools/types.js.map +1 -0
  187. package/dist/tools/web-scrape.js +145 -0
  188. package/dist/tools/web-scrape.js.map +1 -0
  189. package/package.json +93 -0
@@ -0,0 +1,335 @@
1
+ /**
2
+ * MCP Server Handler
3
+ *
4
+ * Implements JSON-RPC 2.0 MCP protocol for exposing this app's tools to external clients.
5
+ * Supports external MCP clients like Claude Desktop, MCP Inspector, etc.
6
+ */
7
+ import { getAllNativeTools, executeNativeTool } from '../tools/index.js';
8
+ import { getConfig } from '../config/loader.js';
9
+ import { registry } from '../registry/index.js';
10
+ // MCP Protocol error codes (from spec)
11
+ const MCP_ERRORS = {
12
+ PARSE_ERROR: -32700,
13
+ INVALID_REQUEST: -32600,
14
+ METHOD_NOT_FOUND: -32601,
15
+ INVALID_PARAMS: -32602,
16
+ INTERNAL_ERROR: -32603,
17
+ };
18
+ /**
19
+ * Convert native tools to MCP tool format
20
+ */
21
+ export function nativeToolsToMCPTools() {
22
+ const nativeTools = getAllNativeTools();
23
+ return nativeTools.map((tool) => ({
24
+ name: tool.name,
25
+ description: tool.description,
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: tool.inputSchema.properties,
29
+ required: tool.inputSchema.required,
30
+ },
31
+ _meta: tool._meta,
32
+ }));
33
+ }
34
+ /**
35
+ * Get all tools to expose via MCP based on config
36
+ */
37
+ function getExposedTools() {
38
+ const config = getConfig();
39
+ const serverConfig = config.mcp?.server;
40
+ if (!serverConfig?.enabled) {
41
+ return [];
42
+ }
43
+ const exposeTools = serverConfig.exposeTools ?? 'native';
44
+ if (exposeTools === 'native') {
45
+ return nativeToolsToMCPTools();
46
+ }
47
+ if (exposeTools === 'all') {
48
+ // Get native tools + any extension-registered tools
49
+ return nativeToolsToMCPTools();
50
+ }
51
+ if (Array.isArray(exposeTools)) {
52
+ // Filter to specific tools by name
53
+ const allTools = nativeToolsToMCPTools();
54
+ return allTools.filter((tool) => exposeTools.includes(tool.name));
55
+ }
56
+ return nativeToolsToMCPTools();
57
+ }
58
+ /**
59
+ * Get MCP resources from registry (extension-provided)
60
+ */
61
+ async function getMCPResources() {
62
+ const resourcesMap = registry.getAll('mcp-resource');
63
+ const resources = [];
64
+ for (const [, resource] of resourcesMap) {
65
+ resources.push({
66
+ uri: resource.uri,
67
+ name: resource.name,
68
+ description: resource.description,
69
+ mimeType: resource.mimeType,
70
+ });
71
+ }
72
+ return resources;
73
+ }
74
+ /**
75
+ * Read an MCP resource by URI
76
+ */
77
+ async function readMCPResource(uri, context) {
78
+ const resourcesMap = registry.getAll('mcp-resource');
79
+ let foundResource = undefined;
80
+ for (const [, resource] of resourcesMap) {
81
+ if (resource.uri === uri) {
82
+ foundResource = resource;
83
+ break;
84
+ }
85
+ }
86
+ if (!foundResource) {
87
+ return null;
88
+ }
89
+ try {
90
+ return await foundResource.read({ userId: context.userId });
91
+ }
92
+ catch (error) {
93
+ console.error(`[MCP Server] Error reading resource ${uri}:`, error);
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * Handle MCP protocol methods
99
+ */
100
+ export async function handleMCPRequest(request, context) {
101
+ const { id, method, params } = request;
102
+ try {
103
+ switch (method) {
104
+ case 'initialize':
105
+ return handleInitialize(id, params);
106
+ case 'ping':
107
+ return handlePing(id);
108
+ case 'tools/list':
109
+ return handleToolsList(id);
110
+ case 'tools/call':
111
+ return await handleToolsCall(id, params, context);
112
+ case 'resources/list':
113
+ return await handleResourcesList(id);
114
+ case 'resources/read':
115
+ return await handleResourcesRead(id, params, context);
116
+ case 'notifications/initialized':
117
+ // Client notification - no response needed
118
+ return { jsonrpc: '2.0', id: null, result: {} };
119
+ default:
120
+ return {
121
+ jsonrpc: '2.0',
122
+ id: id ?? null,
123
+ error: {
124
+ code: MCP_ERRORS.METHOD_NOT_FOUND,
125
+ message: `Method not found: ${method}`,
126
+ },
127
+ };
128
+ }
129
+ }
130
+ catch (error) {
131
+ console.error(`[MCP Server] Error handling ${method}:`, error);
132
+ return {
133
+ jsonrpc: '2.0',
134
+ id: id ?? null,
135
+ error: {
136
+ code: MCP_ERRORS.INTERNAL_ERROR,
137
+ message: error instanceof Error ? error.message : 'Internal error',
138
+ },
139
+ };
140
+ }
141
+ }
142
+ /**
143
+ * Handle initialize method
144
+ */
145
+ function handleInitialize(id, params) {
146
+ const config = getConfig();
147
+ const appName = config.app?.name || 'Chat SaaS';
148
+ return {
149
+ jsonrpc: '2.0',
150
+ id: id ?? null,
151
+ result: {
152
+ protocolVersion: '2024-11-05',
153
+ serverInfo: {
154
+ name: appName,
155
+ version: '1.0.0',
156
+ },
157
+ capabilities: {
158
+ tools: {},
159
+ resources: {},
160
+ },
161
+ },
162
+ };
163
+ }
164
+ /**
165
+ * Handle ping method
166
+ */
167
+ function handlePing(id) {
168
+ return {
169
+ jsonrpc: '2.0',
170
+ id: id ?? null,
171
+ result: {},
172
+ };
173
+ }
174
+ /**
175
+ * Handle tools/list method
176
+ */
177
+ function handleToolsList(id) {
178
+ const tools = getExposedTools();
179
+ return {
180
+ jsonrpc: '2.0',
181
+ id: id ?? null,
182
+ result: {
183
+ tools: tools.map((tool) => ({
184
+ name: tool.name,
185
+ description: tool.description,
186
+ inputSchema: tool.inputSchema,
187
+ _meta: tool._meta,
188
+ })),
189
+ },
190
+ };
191
+ }
192
+ /**
193
+ * Handle tools/call method
194
+ */
195
+ async function handleToolsCall(id, params, context) {
196
+ if (!params?.name || typeof params.name !== 'string') {
197
+ return {
198
+ jsonrpc: '2.0',
199
+ id: id ?? null,
200
+ error: {
201
+ code: MCP_ERRORS.INVALID_PARAMS,
202
+ message: 'Missing required parameter: name',
203
+ },
204
+ };
205
+ }
206
+ const toolName = params.name;
207
+ const args = params.arguments || {};
208
+ // Check if tool is exposed
209
+ const exposedTools = getExposedTools();
210
+ const tool = exposedTools.find((t) => t.name === toolName);
211
+ if (!tool) {
212
+ return {
213
+ jsonrpc: '2.0',
214
+ id: id ?? null,
215
+ error: {
216
+ code: MCP_ERRORS.INVALID_PARAMS,
217
+ message: `Tool not found: ${toolName}`,
218
+ },
219
+ };
220
+ }
221
+ console.log(`[MCP Server] Executing tool: ${toolName}`);
222
+ // Execute the native tool
223
+ const result = await executeNativeTool(toolName, args, {
224
+ userId: context.userId,
225
+ threadId: undefined,
226
+ agentId: undefined,
227
+ });
228
+ return {
229
+ jsonrpc: '2.0',
230
+ id: id ?? null,
231
+ result: {
232
+ content: result.content,
233
+ isError: result.isError,
234
+ structuredContent: result.structuredContent,
235
+ },
236
+ };
237
+ }
238
+ /**
239
+ * Handle resources/list method
240
+ */
241
+ async function handleResourcesList(id) {
242
+ const resources = await getMCPResources();
243
+ return {
244
+ jsonrpc: '2.0',
245
+ id: id ?? null,
246
+ result: {
247
+ resources,
248
+ },
249
+ };
250
+ }
251
+ /**
252
+ * Handle resources/read method
253
+ */
254
+ async function handleResourcesRead(id, params, context) {
255
+ if (!params?.uri || typeof params.uri !== 'string') {
256
+ return {
257
+ jsonrpc: '2.0',
258
+ id: id ?? null,
259
+ error: {
260
+ code: MCP_ERRORS.INVALID_PARAMS,
261
+ message: 'Missing required parameter: uri',
262
+ },
263
+ };
264
+ }
265
+ const uri = params.uri;
266
+ const result = await readMCPResource(uri, context);
267
+ if (!result) {
268
+ return {
269
+ jsonrpc: '2.0',
270
+ id: id ?? null,
271
+ error: {
272
+ code: MCP_ERRORS.INVALID_PARAMS,
273
+ message: `Resource not found: ${uri}`,
274
+ },
275
+ };
276
+ }
277
+ // Get resource metadata
278
+ const resources = await getMCPResources();
279
+ const resourceMeta = resources.find((r) => r.uri === uri);
280
+ return {
281
+ jsonrpc: '2.0',
282
+ id: id ?? null,
283
+ result: {
284
+ contents: [
285
+ {
286
+ uri,
287
+ mimeType: resourceMeta?.mimeType || 'text/plain',
288
+ text: result.text,
289
+ blob: result.blob,
290
+ },
291
+ ],
292
+ },
293
+ };
294
+ }
295
+ /**
296
+ * Parse and validate a JSON-RPC request
297
+ */
298
+ export function parseJsonRpcRequest(body) {
299
+ if (!body || typeof body !== 'object') {
300
+ return {
301
+ code: MCP_ERRORS.PARSE_ERROR,
302
+ message: 'Invalid JSON',
303
+ };
304
+ }
305
+ const req = body;
306
+ if (req.jsonrpc !== '2.0') {
307
+ return {
308
+ code: MCP_ERRORS.INVALID_REQUEST,
309
+ message: 'Invalid JSON-RPC version',
310
+ };
311
+ }
312
+ if (typeof req.method !== 'string') {
313
+ return {
314
+ code: MCP_ERRORS.INVALID_REQUEST,
315
+ message: 'Missing or invalid method',
316
+ };
317
+ }
318
+ return {
319
+ jsonrpc: '2.0',
320
+ id: req.id,
321
+ method: req.method,
322
+ params: req.params,
323
+ };
324
+ }
325
+ /**
326
+ * Create an error response for parse/validation errors
327
+ */
328
+ export function createErrorResponse(error) {
329
+ return {
330
+ jsonrpc: '2.0',
331
+ id: null,
332
+ error,
333
+ };
334
+ }
335
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ import bcrypt from 'bcryptjs';
2
+ import { db } from '@chaaskit/db';
3
+ import { HTTP_STATUS } from '@chaaskit/shared';
4
+ import { getConfig } from '../config/loader.js';
5
+ /**
6
+ * Check if a path matches a pattern.
7
+ * Supports:
8
+ * - Exact match: "/api/threads"
9
+ * - Single segment wildcard: "/api/threads/*" matches "/api/threads/123" but not "/api/threads/123/messages"
10
+ * - Multi-segment wildcard: "/api/threads/**" matches "/api/threads/123" and "/api/threads/123/messages"
11
+ */
12
+ function matchesPattern(path, pattern) {
13
+ // Exact match
14
+ if (pattern === path) {
15
+ return true;
16
+ }
17
+ // Handle ** (matches any depth)
18
+ if (pattern.endsWith('/**')) {
19
+ const prefix = pattern.slice(0, -3);
20
+ return path === prefix || path.startsWith(prefix + '/');
21
+ }
22
+ // Handle * (matches single segment)
23
+ if (pattern.endsWith('/*')) {
24
+ const prefix = pattern.slice(0, -2);
25
+ if (!path.startsWith(prefix + '/')) {
26
+ return false;
27
+ }
28
+ const remainder = path.slice(prefix.length + 1);
29
+ // Should not contain another slash (single segment only)
30
+ return !remainder.includes('/');
31
+ }
32
+ return false;
33
+ }
34
+ /**
35
+ * Check if the request path is allowed for API key access.
36
+ */
37
+ function isEndpointAllowed(path, allowedEndpoints) {
38
+ for (const pattern of allowedEndpoints) {
39
+ if (matchesPattern(path, pattern)) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ /**
46
+ * Middleware to authenticate requests using API keys.
47
+ * Should be used before requireAuth in the middleware chain.
48
+ *
49
+ * API key access is restricted to endpoints listed in config.api.allowedEndpoints.
50
+ * If no endpoints are configured, API keys cannot be used for any endpoint.
51
+ */
52
+ export async function apiKeyAuth(req, res, next) {
53
+ try {
54
+ const authHeader = req.headers.authorization;
55
+ const config = getConfig();
56
+ const keyPrefix = config.api?.keyPrefix || 'sk-';
57
+ // Check if this looks like an API key
58
+ const isApiKeyAuth = authHeader?.startsWith(`Bearer ${keyPrefix}`);
59
+ if (!isApiKeyAuth) {
60
+ return next(); // Not an API key, let other auth handle it
61
+ }
62
+ // It's an API key - check if this endpoint is allowed
63
+ const allowedEndpoints = config.api?.allowedEndpoints || [];
64
+ if (allowedEndpoints.length === 0) {
65
+ res.status(HTTP_STATUS.FORBIDDEN).json({
66
+ error: 'API key access is not enabled for any endpoints'
67
+ });
68
+ return;
69
+ }
70
+ if (!isEndpointAllowed(req.path, allowedEndpoints)) {
71
+ res.status(HTTP_STATUS.FORBIDDEN).json({
72
+ error: 'API key access is not allowed for this endpoint'
73
+ });
74
+ return;
75
+ }
76
+ const apiKey = authHeader.slice(7); // Remove "Bearer "
77
+ // Find keys by prefix and check hash
78
+ // The stored keyPrefix is: configuredPrefix + 6 random chars
79
+ const storedPrefixLength = keyPrefix.length + 6;
80
+ const searchPrefix = apiKey.slice(0, storedPrefixLength);
81
+ const candidates = await db.apiKey.findMany({
82
+ where: { keyPrefix: searchPrefix },
83
+ include: {
84
+ user: {
85
+ select: {
86
+ id: true,
87
+ email: true,
88
+ name: true,
89
+ avatarUrl: true,
90
+ isAdmin: true,
91
+ emailVerified: true,
92
+ plan: true,
93
+ credits: true,
94
+ messagesThisMonth: true,
95
+ themePreference: true,
96
+ },
97
+ },
98
+ team: true,
99
+ },
100
+ });
101
+ for (const candidate of candidates) {
102
+ if (await bcrypt.compare(apiKey, candidate.keyHash)) {
103
+ // Check expiration
104
+ if (candidate.expiresAt && candidate.expiresAt < new Date()) {
105
+ res.status(HTTP_STATUS.UNAUTHORIZED).json({ error: 'API key expired' });
106
+ return;
107
+ }
108
+ // If team-scoped, verify user is still a member
109
+ if (candidate.teamId) {
110
+ const membership = await db.teamMember.findFirst({
111
+ where: { userId: candidate.userId, teamId: candidate.teamId },
112
+ });
113
+ if (!membership) {
114
+ res.status(HTTP_STATUS.UNAUTHORIZED).json({ error: 'API key invalid - no longer a team member' });
115
+ return;
116
+ }
117
+ }
118
+ // Update lastUsedAt (fire and forget)
119
+ db.apiKey.update({
120
+ where: { id: candidate.id },
121
+ data: { lastUsedAt: new Date() },
122
+ }).catch(() => { });
123
+ req.user = candidate.user;
124
+ // Set team context for team-scoped keys
125
+ req.apiKeyTeamId = candidate.teamId || undefined;
126
+ return next();
127
+ }
128
+ }
129
+ // No valid key found
130
+ res.status(HTTP_STATUS.UNAUTHORIZED).json({ error: 'Invalid API key' });
131
+ }
132
+ catch (error) {
133
+ next(error);
134
+ }
135
+ }
136
+ //# sourceMappingURL=apiKeyAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiKeyAuth.js","sourceRoot":"","sources":["../../src/middleware/apiKeyAuth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import { db } from '@chaaskit/db';
3
+ import { AppError } from './errorHandler.js';
4
+ import { HTTP_STATUS } from '@chaaskit/shared';
5
+ import { getConfig } from '../config/loader.js';
6
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
7
+ export function generateToken(userId, email) {
8
+ return jwt.sign({ userId, email }, JWT_SECRET, { expiresIn: '7d' });
9
+ }
10
+ export function verifyToken(token) {
11
+ return jwt.verify(token, JWT_SECRET);
12
+ }
13
+ export async function requireAuth(req, res, next) {
14
+ try {
15
+ // If user is already authenticated (e.g., by apiKeyAuth middleware), skip JWT validation
16
+ if (req.user) {
17
+ next();
18
+ return;
19
+ }
20
+ const config = getConfig();
21
+ // Check for token in Authorization header or cookie
22
+ const authHeader = req.headers.authorization;
23
+ const cookieToken = req.cookies?.token;
24
+ const token = authHeader?.startsWith('Bearer ')
25
+ ? authHeader.slice(7)
26
+ : cookieToken;
27
+ if (!token) {
28
+ // Allow unauthenticated access if configured
29
+ if (config.auth.allowUnauthenticated) {
30
+ next();
31
+ return;
32
+ }
33
+ throw new AppError(HTTP_STATUS.UNAUTHORIZED, 'Authentication required');
34
+ }
35
+ const payload = verifyToken(token);
36
+ const user = await db.user.findUnique({
37
+ where: { id: payload.userId },
38
+ select: {
39
+ id: true,
40
+ email: true,
41
+ name: true,
42
+ avatarUrl: true,
43
+ isAdmin: true,
44
+ emailVerified: true,
45
+ plan: true,
46
+ credits: true,
47
+ messagesThisMonth: true,
48
+ themePreference: true,
49
+ },
50
+ });
51
+ if (!user) {
52
+ throw new AppError(HTTP_STATUS.UNAUTHORIZED, 'User not found');
53
+ }
54
+ req.user = user;
55
+ next();
56
+ }
57
+ catch (error) {
58
+ if (error instanceof AppError) {
59
+ next(error);
60
+ return;
61
+ }
62
+ next(new AppError(HTTP_STATUS.UNAUTHORIZED, 'Invalid token'));
63
+ }
64
+ }
65
+ export async function optionalAuth(req, res, next) {
66
+ try {
67
+ // If user is already authenticated (e.g., by apiKeyAuth middleware), skip JWT validation
68
+ if (req.user) {
69
+ next();
70
+ return;
71
+ }
72
+ const authHeader = req.headers.authorization;
73
+ const cookieToken = req.cookies?.token;
74
+ const token = authHeader?.startsWith('Bearer ')
75
+ ? authHeader.slice(7)
76
+ : cookieToken;
77
+ if (!token) {
78
+ next();
79
+ return;
80
+ }
81
+ const payload = verifyToken(token);
82
+ const user = await db.user.findUnique({
83
+ where: { id: payload.userId },
84
+ select: {
85
+ id: true,
86
+ email: true,
87
+ name: true,
88
+ avatarUrl: true,
89
+ isAdmin: true,
90
+ emailVerified: true,
91
+ plan: true,
92
+ credits: true,
93
+ messagesThisMonth: true,
94
+ themePreference: true,
95
+ },
96
+ });
97
+ if (user) {
98
+ req.user = user;
99
+ }
100
+ next();
101
+ }
102
+ catch {
103
+ // Invalid token, continue without auth
104
+ next();
105
+ }
106
+ }
107
+ export async function requireAdmin(req, res, next) {
108
+ if (!req.user) {
109
+ next(new AppError(HTTP_STATUS.UNAUTHORIZED, 'Authentication required'));
110
+ return;
111
+ }
112
+ const config = getConfig();
113
+ const adminEmails = config.admin?.emails || [];
114
+ // Check if user's email is in the admin list
115
+ const isConfigAdmin = adminEmails.some((email) => email.toLowerCase() === req.user.email.toLowerCase());
116
+ // Also check the database isAdmin flag for backward compatibility
117
+ if (!isConfigAdmin && !req.user.isAdmin) {
118
+ next(new AppError(HTTP_STATUS.FORBIDDEN, 'Admin access required'));
119
+ return;
120
+ }
121
+ next();
122
+ }
123
+ import { isEmailEnabled } from '../services/email/index.js';
124
+ /**
125
+ * Check if email verification is required for the current user.
126
+ * Returns true if user needs verification, false otherwise.
127
+ */
128
+ function shouldBlockUnverifiedUser(req) {
129
+ const config = getConfig();
130
+ // No user = nothing to verify
131
+ if (!req.user) {
132
+ return false;
133
+ }
134
+ // Feature not enabled
135
+ if (!config.auth.emailVerification?.enabled) {
136
+ return false;
137
+ }
138
+ // Email provider not configured (graceful degradation)
139
+ if (!isEmailEnabled()) {
140
+ return false;
141
+ }
142
+ // User is already verified
143
+ if (req.user.emailVerified) {
144
+ return false;
145
+ }
146
+ // User needs verification
147
+ return true;
148
+ }
149
+ /**
150
+ * Middleware that requires the user to have a verified email.
151
+ * Skips the check if email verification is not enabled or email provider is not configured.
152
+ * Use after requireAuth middleware.
153
+ */
154
+ export async function requireVerifiedEmail(req, res, next) {
155
+ if (!req.user) {
156
+ next(new AppError(HTTP_STATUS.UNAUTHORIZED, 'Authentication required'));
157
+ return;
158
+ }
159
+ if (shouldBlockUnverifiedUser(req)) {
160
+ res.status(HTTP_STATUS.FORBIDDEN).json({
161
+ error: 'Email not verified',
162
+ code: 'EMAIL_NOT_VERIFIED',
163
+ message: 'Please verify your email address to continue',
164
+ });
165
+ return;
166
+ }
167
+ next();
168
+ }
169
+ /**
170
+ * Middleware that checks email verification for optional auth routes.
171
+ * If user is authenticated but not verified, blocks the request.
172
+ * If user is not authenticated, allows the request (for allowUnauthenticated mode).
173
+ * Use after optionalAuth middleware.
174
+ */
175
+ export async function optionalVerifiedEmail(req, res, next) {
176
+ // If no user, allow (unauthenticated access may be allowed)
177
+ if (!req.user) {
178
+ next();
179
+ return;
180
+ }
181
+ // If user exists but needs verification, block
182
+ if (shouldBlockUnverifiedUser(req)) {
183
+ res.status(HTTP_STATUS.FORBIDDEN).json({
184
+ error: 'Email not verified',
185
+ code: 'EMAIL_NOT_VERIFIED',
186
+ message: 'Please verify your email address to continue',
187
+ });
188
+ return;
189
+ }
190
+ next();
191
+ }
192
+ //# sourceMappingURL=auth.js.map