@studious-lms/server 1.4.1 → 1.4.3

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 (248) hide show
  1. package/.coderabbit.yaml +4 -1
  2. package/.env.example +6 -0
  3. package/.env.test.example +2 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +101 -76
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/config/cors.d.ts +16 -0
  8. package/dist/lib/config/cors.d.ts.map +1 -0
  9. package/dist/lib/config/cors.js +75 -0
  10. package/dist/lib/config/cors.js.map +1 -0
  11. package/dist/lib/config/env.d.ts +14 -0
  12. package/dist/lib/config/env.d.ts.map +1 -1
  13. package/dist/lib/config/env.js +9 -2
  14. package/dist/lib/config/env.js.map +1 -1
  15. package/dist/lib/fileUpload.d.ts +10 -2
  16. package/dist/lib/fileUpload.d.ts.map +1 -1
  17. package/dist/lib/fileUpload.js +38 -6
  18. package/dist/lib/fileUpload.js.map +1 -1
  19. package/dist/lib/prisma.d.ts +14 -2
  20. package/dist/lib/prisma.d.ts.map +1 -1
  21. package/dist/lib/prisma.js +27 -8
  22. package/dist/lib/prisma.js.map +1 -1
  23. package/dist/middleware/auth.d.ts.map +1 -1
  24. package/dist/middleware/auth.js +18 -5
  25. package/dist/middleware/auth.js.map +1 -1
  26. package/dist/middleware/security.d.ts.map +1 -1
  27. package/dist/middleware/security.js +3 -3
  28. package/dist/middleware/security.js.map +1 -1
  29. package/dist/models/agenda.d.ts +16 -16
  30. package/dist/models/announcement.d.ts +59 -23
  31. package/dist/models/announcement.d.ts.map +1 -1
  32. package/dist/models/assignment.d.ts +363 -276
  33. package/dist/models/assignment.d.ts.map +1 -1
  34. package/dist/models/attendance.d.ts +63 -21
  35. package/dist/models/attendance.d.ts.map +1 -1
  36. package/dist/models/auth.d.ts +115 -26
  37. package/dist/models/auth.d.ts.map +1 -1
  38. package/dist/models/auth.js +53 -30
  39. package/dist/models/auth.js.map +1 -1
  40. package/dist/models/class.d.ts +115 -65
  41. package/dist/models/class.d.ts.map +1 -1
  42. package/dist/models/class.js +16 -4
  43. package/dist/models/class.js.map +1 -1
  44. package/dist/models/comment.d.ts +52 -16
  45. package/dist/models/comment.d.ts.map +1 -1
  46. package/dist/models/conversation.d.ts +46 -16
  47. package/dist/models/conversation.d.ts.map +1 -1
  48. package/dist/models/event.d.ts +107 -53
  49. package/dist/models/event.d.ts.map +1 -1
  50. package/dist/models/file.d.ts +213 -165
  51. package/dist/models/file.d.ts.map +1 -1
  52. package/dist/models/folder.d.ts +161 -77
  53. package/dist/models/folder.d.ts.map +1 -1
  54. package/dist/models/labChat.d.ts +73 -31
  55. package/dist/models/labChat.d.ts.map +1 -1
  56. package/dist/models/marketing.d.ts +25 -7
  57. package/dist/models/marketing.d.ts.map +1 -1
  58. package/dist/models/message.d.ts +31 -13
  59. package/dist/models/message.d.ts.map +1 -1
  60. package/dist/models/newtonChat.d.ts +34 -10
  61. package/dist/models/newtonChat.d.ts.map +1 -1
  62. package/dist/models/notification.d.ts +29 -10
  63. package/dist/models/notification.d.ts.map +1 -1
  64. package/dist/models/notification.js +13 -9
  65. package/dist/models/notification.js.map +1 -1
  66. package/dist/models/section.d.ts +71 -23
  67. package/dist/models/section.d.ts.map +1 -1
  68. package/dist/models/user.d.ts +27 -9
  69. package/dist/models/user.d.ts.map +1 -1
  70. package/dist/models/worksheet.d.ts +606 -106
  71. package/dist/models/worksheet.d.ts.map +1 -1
  72. package/dist/models/worksheet.js +168 -2
  73. package/dist/models/worksheet.js.map +1 -1
  74. package/dist/pipelines/aiLabChat.d.ts +22 -2
  75. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  76. package/dist/pipelines/aiLabChat.js +133 -95
  77. package/dist/pipelines/aiLabChat.js.map +1 -1
  78. package/dist/pipelines/aiLabChatContract.d.ts +25 -25
  79. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  80. package/dist/pipelines/aiNewtonChat.js +25 -14
  81. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  82. package/dist/pipelines/gradeWorksheet.d.ts +8 -8
  83. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  84. package/dist/pipelines/gradeWorksheet.js +12 -5
  85. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  86. package/dist/pipelines/labChatPrompt.d.ts +27 -0
  87. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  88. package/dist/pipelines/labChatPrompt.js +143 -69
  89. package/dist/pipelines/labChatPrompt.js.map +1 -1
  90. package/dist/routers/_app.d.ts +1579 -1257
  91. package/dist/routers/_app.d.ts.map +1 -1
  92. package/dist/routers/agenda.d.ts +16 -16
  93. package/dist/routers/announcement.d.ts +19 -19
  94. package/dist/routers/assignment.d.ts +307 -291
  95. package/dist/routers/assignment.d.ts.map +1 -1
  96. package/dist/routers/assignment.js +3 -2
  97. package/dist/routers/assignment.js.map +1 -1
  98. package/dist/routers/attendance.d.ts +7 -7
  99. package/dist/routers/auth.d.ts +1 -1
  100. package/dist/routers/class.d.ts +79 -72
  101. package/dist/routers/class.d.ts.map +1 -1
  102. package/dist/routers/class.js +3 -3
  103. package/dist/routers/class.js.map +1 -1
  104. package/dist/routers/comment.d.ts +6 -6
  105. package/dist/routers/conversation.d.ts +11 -11
  106. package/dist/routers/event.d.ts +35 -35
  107. package/dist/routers/file.d.ts +12 -12
  108. package/dist/routers/folder.d.ts +54 -54
  109. package/dist/routers/labChat.d.ts +18 -12
  110. package/dist/routers/labChat.d.ts.map +1 -1
  111. package/dist/routers/labChat.js +5 -3
  112. package/dist/routers/labChat.js.map +1 -1
  113. package/dist/routers/marketing.d.ts +2 -2
  114. package/dist/routers/message.d.ts +33 -2
  115. package/dist/routers/message.d.ts.map +1 -1
  116. package/dist/routers/message.js +27 -4
  117. package/dist/routers/message.js.map +1 -1
  118. package/dist/routers/newtonChat.d.ts +7 -1
  119. package/dist/routers/newtonChat.d.ts.map +1 -1
  120. package/dist/routers/newtonChat.js +5 -4
  121. package/dist/routers/newtonChat.js.map +1 -1
  122. package/dist/routers/notifications.d.ts +13 -6
  123. package/dist/routers/notifications.d.ts.map +1 -1
  124. package/dist/routers/notifications.js +4 -4
  125. package/dist/routers/notifications.js.map +1 -1
  126. package/dist/routers/section.d.ts +7 -7
  127. package/dist/routers/studentProgress.d.ts +86 -0
  128. package/dist/routers/studentProgress.d.ts.map +1 -1
  129. package/dist/routers/studentProgress.js +14 -4
  130. package/dist/routers/studentProgress.js.map +1 -1
  131. package/dist/routers/user.d.ts +1 -1
  132. package/dist/routers/worksheet.d.ts +64 -62
  133. package/dist/routers/worksheet.d.ts.map +1 -1
  134. package/dist/routers/worksheet.js +18 -16
  135. package/dist/routers/worksheet.js.map +1 -1
  136. package/dist/seedDatabase.d.ts +1 -1
  137. package/dist/services/agenda.d.ts +16 -16
  138. package/dist/services/announcement.d.ts +8 -8
  139. package/dist/services/assignment.d.ts +299 -283
  140. package/dist/services/assignment.d.ts.map +1 -1
  141. package/dist/services/assignment.js +24 -5
  142. package/dist/services/assignment.js.map +1 -1
  143. package/dist/services/attendance.d.ts +7 -7
  144. package/dist/services/auth.d.ts +2 -2
  145. package/dist/services/auth.d.ts.map +1 -1
  146. package/dist/services/auth.js +19 -12
  147. package/dist/services/auth.js.map +1 -1
  148. package/dist/services/class.d.ts +74 -68
  149. package/dist/services/class.d.ts.map +1 -1
  150. package/dist/services/class.js +6 -3
  151. package/dist/services/class.js.map +1 -1
  152. package/dist/services/comment.d.ts +6 -6
  153. package/dist/services/conversation.d.ts +11 -11
  154. package/dist/services/event.d.ts +31 -31
  155. package/dist/services/file.d.ts +12 -12
  156. package/dist/services/file.d.ts.map +1 -1
  157. package/dist/services/file.js +7 -2
  158. package/dist/services/file.js.map +1 -1
  159. package/dist/services/folder.d.ts +52 -52
  160. package/dist/services/labChat.d.ts +18 -12
  161. package/dist/services/labChat.d.ts.map +1 -1
  162. package/dist/services/labChat.js +30 -18
  163. package/dist/services/labChat.js.map +1 -1
  164. package/dist/services/marketing.d.ts +2 -2
  165. package/dist/services/message.d.ts +30 -0
  166. package/dist/services/message.d.ts.map +1 -1
  167. package/dist/services/message.js +97 -21
  168. package/dist/services/message.js.map +1 -1
  169. package/dist/services/newtonChat.d.ts +6 -1
  170. package/dist/services/newtonChat.d.ts.map +1 -1
  171. package/dist/services/newtonChat.js +36 -18
  172. package/dist/services/newtonChat.js.map +1 -1
  173. package/dist/services/notification.d.ts +16 -9
  174. package/dist/services/notification.d.ts.map +1 -1
  175. package/dist/services/notification.js +24 -9
  176. package/dist/services/notification.js.map +1 -1
  177. package/dist/services/section.d.ts +6 -6
  178. package/dist/services/studentProgress.d.ts +75 -0
  179. package/dist/services/studentProgress.d.ts.map +1 -1
  180. package/dist/services/studentProgress.js +300 -107
  181. package/dist/services/studentProgress.js.map +1 -1
  182. package/dist/services/worksheet.d.ts +64 -64
  183. package/dist/services/worksheet.d.ts.map +1 -1
  184. package/dist/services/worksheet.js +138 -54
  185. package/dist/services/worksheet.js.map +1 -1
  186. package/dist/trpc.d.ts.map +1 -1
  187. package/dist/trpc.js +11 -17
  188. package/dist/trpc.js.map +1 -1
  189. package/dist/utils/inference.d.ts +9 -12
  190. package/dist/utils/inference.d.ts.map +1 -1
  191. package/dist/utils/inference.js +135 -95
  192. package/dist/utils/inference.js.map +1 -1
  193. package/package.json +1 -1
  194. package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +14 -0
  195. package/prisma/migrations/20260415103500_add_session_type/migration.sql +33 -0
  196. package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +19 -0
  197. package/prisma/schema.prisma +41 -1
  198. package/scripts/test-pre-push.ts +6 -2
  199. package/src/index.ts +119 -79
  200. package/src/lib/config/cors.ts +96 -0
  201. package/src/lib/config/env.ts +12 -1
  202. package/src/lib/fileUpload.ts +55 -5
  203. package/src/lib/prisma.ts +25 -6
  204. package/src/middleware/auth.ts +19 -4
  205. package/src/middleware/security.ts +1 -1
  206. package/src/models/auth.ts +59 -29
  207. package/src/models/class.ts +15 -4
  208. package/src/models/notification.ts +13 -7
  209. package/src/models/worksheet.ts +202 -0
  210. package/src/pipelines/aiLabChat.ts +183 -104
  211. package/src/pipelines/aiNewtonChat.ts +24 -14
  212. package/src/pipelines/gradeWorksheet.ts +10 -2
  213. package/src/pipelines/labChatPrompt.ts +196 -68
  214. package/src/routers/assignment.ts +1 -0
  215. package/src/routers/class.ts +2 -2
  216. package/src/routers/labChat.ts +3 -1
  217. package/src/routers/message.ts +38 -1
  218. package/src/routers/newtonChat.ts +4 -2
  219. package/src/routers/notifications.ts +2 -2
  220. package/src/routers/studentProgress.ts +25 -1
  221. package/src/routers/worksheet.ts +31 -19
  222. package/src/services/assignment.ts +30 -2
  223. package/src/services/auth.ts +26 -12
  224. package/src/services/class.ts +4 -1
  225. package/src/services/file.ts +6 -0
  226. package/src/services/labChat.ts +42 -18
  227. package/src/services/message.ts +163 -18
  228. package/src/services/newtonChat.ts +49 -18
  229. package/src/services/notification.ts +27 -8
  230. package/src/services/studentProgress.ts +425 -121
  231. package/src/services/worksheet.ts +191 -51
  232. package/src/trpc.ts +12 -18
  233. package/src/utils/inference.ts +205 -112
  234. package/tests/globalSetup.ts +13 -4
  235. package/tests/lib/cors.test.ts +103 -0
  236. package/tests/pipelines/aiLabChat.test.ts +64 -84
  237. package/tests/routers/auth.test.ts +120 -17
  238. package/tests/routers/class.test.ts +7 -1
  239. package/tests/routers/message.test.ts +236 -9
  240. package/tests/routers/newtonChat.test.ts +81 -0
  241. package/tests/routers/notification.test.ts +12 -0
  242. package/tests/routers/studentProgress.test.ts +2 -31
  243. package/tests/routers/worksheet.test.ts +194 -0
  244. package/tests/setup.ts +22 -5
  245. package/tests/utils/aiLabChatPrompt.test.ts +114 -6
  246. package/tests/utils/inference.test.ts +218 -0
  247. package/tests/utils/studentProgress.test.ts +361 -0
  248. package/vitest.unit.config.ts +1 -0
package/.coderabbit.yaml CHANGED
@@ -1,9 +1,12 @@
1
1
  # Raise the file limit
2
2
  reviews:
3
3
  review_status: true
4
+ auto_review:
5
+ base_branches:
6
+ - staging
4
7
  path_filters:
5
8
  # Exclude paths you don't want reviewed
6
9
  - "!**/node_modules/**"
7
10
  - "!**/dist/**"
8
11
  - "!**/*.lock"
9
- - "!**/package-lock.json"
12
+ - "!**/package-lock.json"
package/.env.example CHANGED
@@ -9,6 +9,12 @@ DIRECT_URL="postgresql://postgres:password@localhost:5432/postgres"
9
9
  # Application Configuration
10
10
  # Frontend URL for CORS configuration
11
11
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
12
+ # Extra frontend origins allowed to call the API, comma-separated.
13
+ # Example: CORS_ALLOWED_ORIGINS="https://studious-git-replicas-stu-15-coderabbit-gua-8eec83-studious-lms.vercel.app"
14
+ CORS_ALLOWED_ORIGINS=""
15
+ # Extra frontend origin regex patterns, comma-separated. Keep these narrow.
16
+ # Example: CORS_ALLOWED_ORIGIN_PATTERNS="^https://studious-git-[a-z0-9-]+-studious-lms\.vercel\.app$"
17
+ CORS_ALLOWED_ORIGIN_PATTERNS=""
12
18
  # Server port
13
19
  PORT=3001
14
20
  # Environment: development, test, production
package/.env.test.example CHANGED
@@ -7,6 +7,8 @@ DIRECT_URL="postgresql://postgres:password@localhost:5434/postgres"
7
7
 
8
8
  # Application Configuration
9
9
  NEXT_PUBLIC_APP_URL="http://localhost:3000"
10
+ CORS_ALLOWED_ORIGINS=""
11
+ CORS_ALLOWED_ORIGIN_PATTERNS=""
10
12
  PORT=3002
11
13
  NODE_ENV=test
12
14
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAwBA,OAAO,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AA2BA,OAAO,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e28b4205-9ed2-5148-b94c-3326b3073261")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3cfa0c9c-5588-5fbd-a283-4495b71df718")}catch(e){}}();
3
3
  import express from 'express';
4
4
  import { createServer } from 'http';
5
5
  import { Server } from 'socket.io';
@@ -7,12 +7,15 @@ import cors from 'cors';
7
7
  import { createExpressMiddleware } from '@trpc/server/adapters/express';
8
8
  import { appRouter } from './routers/_app.js';
9
9
  import { createTRPCContext, createCallerFactory } from './trpc.js';
10
+ import { findUserBySessionToken } from './models/auth.js';
10
11
  import { logger } from './utils/logger.js';
11
12
  import { setupSocketHandlers } from './socket/handlers.js';
12
13
  import { bucket } from './lib/googleCloudStorage.js';
14
+ import { validateDirectUploadIntent } from './lib/fileUpload.js';
13
15
  import { prisma } from './lib/prisma.js';
14
16
  import { pusher } from './lib/pusher.js';
15
17
  import { connectRedis, disconnectRedis } from './lib/redis.js';
18
+ import { createCorsOriginMatcher } from './lib/config/cors.js';
16
19
  import { authLimiter, generalLimiter, helmetConfig, uploadLimiter } from './middleware/security.js';
17
20
  import * as Sentry from "@sentry/node";
18
21
  import { env } from './lib/config/env.js';
@@ -20,6 +23,7 @@ import compression from 'compression';
20
23
  import { v4 as uuidv4 } from 'uuid';
21
24
  import "./instrument.js";
22
25
  const app = express();
26
+ app.set('trust proxy', 1);
23
27
  app.use(helmetConfig);
24
28
  app.use(compression());
25
29
  app.use(express.json());
@@ -29,30 +33,22 @@ app.use((req, res, next) => {
29
33
  res.setHeader('X-Request-ID', requestId);
30
34
  next();
31
35
  });
32
- const allowedOrigins = env.NODE_ENV === 'production'
33
- ? [
34
- 'https://www.studious.sh',
35
- 'https://studious.sh',
36
- 'https://dev.studious.sh',
37
- 'https://www.dev.studious.sh',
38
- env.NEXT_PUBLIC_APP_URL,
39
- 'http://localhost:3000',
40
- ].filter(Boolean)
41
- : [
42
- 'http://localhost:3000',
43
- 'http://localhost:3001',
44
- 'http://127.0.0.1:3000',
45
- 'http://127.0.0.1:3001',
46
- env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
47
- ];
36
+ const { allowedOrigins, allowedOriginPatterns, isAllowedOrigin } = createCorsOriginMatcher(env);
48
37
  // CORS middleware
49
38
  app.use(cors({
50
- origin: allowedOrigins,
39
+ origin: (origin, callback) => {
40
+ if (!origin || isAllowedOrigin(origin)) {
41
+ callback(null, true);
42
+ return;
43
+ }
44
+ logger.warn('CORS origin rejected', { origin, allowedOrigins });
45
+ callback(null, false);
46
+ },
51
47
  credentials: true,
52
48
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
53
49
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-user'],
54
- preflightContinue: false, // Important: stop further handling of OPTIONS
55
- optionsSuccessStatus: 204, // Recommended for modern browsers
50
+ preflightContinue: false,
51
+ optionsSuccessStatus: 204,
56
52
  }));
57
53
  app.use(generalLimiter);
58
54
  // CORS debugging middleware
@@ -100,6 +96,16 @@ app.use((req, res, next) => {
100
96
  // });
101
97
  // Create HTTP server
102
98
  const httpServer = createServer(app);
99
+ app.get('/', async (req, res) => {
100
+ res.status(200).json({
101
+ status: 'OK',
102
+ timestamp: new Date().toISOString(),
103
+ uptime: process.uptime(),
104
+ version: process.env.npm_package_version,
105
+ environment: process.env.NODE_ENV,
106
+ appUrl: process.env.NEXT_PUBLIC_APP_URL,
107
+ });
108
+ });
103
109
  app.get('/health', async (req, res) => {
104
110
  try {
105
111
  // Check database connectivity
@@ -136,10 +142,7 @@ app.post('/api/pusher/auth', async (req, res) => {
136
142
  if (!token) {
137
143
  return res.status(401).json({ error: 'Authentication required' });
138
144
  }
139
- const user = await prisma.user.findFirst({
140
- where: { sessions: { some: { id: token } } },
141
- select: { id: true, username: true },
142
- });
145
+ const user = await findUserBySessionToken(token);
143
146
  if (!user) {
144
147
  return res.status(401).json({ error: 'Invalid or expired session' });
145
148
  }
@@ -208,15 +211,9 @@ app.post('/api/pusher/auth', async (req, res) => {
208
211
  // Setup Socket.IO
209
212
  const io = new Server(httpServer, {
210
213
  cors: {
211
- origin: [
212
- 'http://localhost:3000', // Frontend development server
213
- 'http://localhost:3001', // Server port
214
- 'http://127.0.0.1:3000', // Alternative localhost
215
- 'http://127.0.0.1:3001', // Alternative localhost
216
- 'https://www.studious.sh', // Production frontend
217
- 'https://studious.sh', // Production frontend (without www)
218
- env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
219
- ],
214
+ origin: (origin, callback) => {
215
+ callback(null, !origin || isAllowedOrigin(origin));
216
+ },
220
217
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
221
218
  credentials: true,
222
219
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Access-Control-Allow-Origin', 'x-user']
@@ -246,19 +243,7 @@ app.get('/api/files/:fileId', async (req, res) => {
246
243
  }
247
244
  const token = typeof userHeader === 'string' ? userHeader : userHeader[0];
248
245
  // Find user by session token
249
- const user = await prisma.user.findFirst({
250
- where: {
251
- sessions: {
252
- some: {
253
- id: token
254
- }
255
- }
256
- },
257
- select: {
258
- id: true,
259
- username: true,
260
- }
261
- });
246
+ const user = await findUserBySessionToken(token);
262
247
  if (!user) {
263
248
  return res.status(401).json({ error: 'Invalid or expired session' });
264
249
  }
@@ -417,47 +402,73 @@ app.get('/api/files/:fileId', async (req, res) => {
417
402
  });
418
403
  app.use('/trpc/auth.login', authLimiter);
419
404
  app.use('/trpc/auth.register', authLimiter);
405
+ async function getAuthenticatedRequestUser(req) {
406
+ const userHeader = req.headers['x-user'];
407
+ const tokenFromUserHeader = typeof userHeader === 'string' ? userHeader : userHeader?.[0];
408
+ const authorizationHeader = req.headers.authorization;
409
+ const tokenFromAuthorization = typeof authorizationHeader === 'string' && authorizationHeader.startsWith('Bearer ')
410
+ ? authorizationHeader.slice(7)
411
+ : null;
412
+ let token = tokenFromUserHeader ?? tokenFromAuthorization;
413
+ if (!token && req.headers.cookie) {
414
+ const cookieName = env.PUSHER_AUTH_COOKIE_NAME || 'token';
415
+ const match = req.headers.cookie.match(new RegExp(cookieName + '=([^;]+)'));
416
+ token = match?.[1]?.trim() ?? null;
417
+ }
418
+ if (!token) {
419
+ return null;
420
+ }
421
+ return findUserBySessionToken(token);
422
+ }
423
+ function getUploadSessionId(req) {
424
+ const queryValue = req.query.uploadSessionId;
425
+ if (typeof queryValue === 'string' && queryValue.trim()) {
426
+ return queryValue;
427
+ }
428
+ const headerValue = req.headers['x-upload-session-id'];
429
+ return typeof headerValue === 'string' ? headerValue : headerValue?.[0];
430
+ }
420
431
  // File upload endpoint for secure file uploads (supports both POST and PUT)
421
432
  app.post('/api/upload/:filePath', uploadLimiter, async (req, res) => {
422
- handleFileUpload(req, res);
433
+ await handleFileUpload(req, res);
423
434
  });
424
435
  app.put('/api/upload/:filePath', uploadLimiter, async (req, res) => {
425
- handleFileUpload(req, res);
436
+ await handleFileUpload(req, res);
426
437
  });
427
- function handleFileUpload(req, res) {
438
+ async function handleFileUpload(req, res) {
428
439
  try {
429
440
  const filePath = decodeURIComponent(req.params.filePath);
430
441
  // Set CORS headers for upload endpoint
431
442
  const origin = req.headers.origin;
432
- const allowedOrigins = [
433
- 'http://localhost:3000',
434
- 'http://localhost:3001',
435
- 'http://127.0.0.1:3000',
436
- 'http://127.0.0.1:3001',
437
- 'https://www.studious.sh', // Production frontend
438
- 'https://studious.sh', // Production frontend (without www)
439
- env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
440
- ];
441
- if (origin && allowedOrigins.includes(origin)) {
442
- res.header('Access-Control-Allow-Origin', origin);
443
+ if (origin && !isAllowedOrigin(origin)) {
444
+ return res.status(403).json({ error: 'Origin not allowed' });
443
445
  }
444
- else {
445
- res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
446
+ if (origin) {
447
+ res.header('Access-Control-Allow-Origin', origin);
446
448
  }
447
449
  res.header('Access-Control-Allow-Credentials', 'true');
448
- // Get content type from headers
450
+ const user = await getAuthenticatedRequestUser(req);
451
+ if (!user) {
452
+ return res.status(401).json({ error: 'Authentication required' });
453
+ }
454
+ const uploadSessionId = getUploadSessionId(req);
455
+ if (!uploadSessionId) {
456
+ return res.status(401).json({ error: 'Upload session is required' });
457
+ }
458
+ await validateDirectUploadIntent({
459
+ filePath,
460
+ uploadSessionId,
461
+ userId: user.id,
462
+ });
449
463
  const contentType = req.headers['content-type'] || 'application/octet-stream';
450
- // Create a new file in the bucket
451
464
  const file = bucket.file(filePath);
452
- // Create a write stream to Google Cloud Storage
453
465
  const writeStream = file.createWriteStream({
454
466
  metadata: {
455
467
  contentType,
456
468
  },
457
469
  });
458
- // Handle stream events
459
470
  writeStream.on('error', (error) => {
460
- logger.error('Error uploading file:', { error });
471
+ logger.error('Error uploading file:', { error, filePath, userId: user.id });
461
472
  if (!res.headersSent) {
462
473
  res.status(500).json({ error: 'Error uploading file' });
463
474
  }
@@ -469,16 +480,35 @@ function handleFileUpload(req, res) {
469
480
  message: 'File uploaded successfully'
470
481
  });
471
482
  });
472
- // Pipe the request body to the write stream
473
483
  req.pipe(writeStream);
474
484
  }
475
485
  catch (error) {
476
486
  logger.error('Error handling file upload:', { error });
487
+ const code = typeof error === 'object' && error && 'code' in error ? error.code : null;
488
+ const message = error instanceof Error ? error.message : 'Internal server error';
489
+ if (code === 'FORBIDDEN') {
490
+ return res.status(403).json({ error: message });
491
+ }
492
+ if (code === 'BAD_REQUEST') {
493
+ return res.status(400).json({ error: message });
494
+ }
477
495
  res.status(500).json({ error: 'Internal server error' });
478
496
  }
479
497
  }
480
498
  // Create caller
481
499
  const createCaller = createCallerFactory(appRouter);
500
+ // Fallback OPTIONS handler — prevents tRPC from rejecting preflight requests
501
+ // when the cors middleware passes through without ending the response.
502
+ app.options('/trpc/*', (req, res) => {
503
+ const origin = req.headers.origin;
504
+ if (origin && isAllowedOrigin(origin)) {
505
+ res.setHeader('Access-Control-Allow-Origin', origin);
506
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
507
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
508
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, x-user');
509
+ }
510
+ res.sendStatus(204);
511
+ });
482
512
  // Setup tRPC middleware
483
513
  app.use('/trpc', createExpressMiddleware({
484
514
  router: appRouter,
@@ -513,13 +543,8 @@ logger.info('Configurations', {
513
543
  });
514
544
  // Log CORS configuration
515
545
  logger.info('CORS Configuration', {
516
- allowedOrigins: [
517
- 'http://localhost:3000',
518
- 'http://localhost:3001',
519
- 'http://127.0.0.1:3000',
520
- 'http://127.0.0.1:3001',
521
- env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
522
- ]
546
+ allowedOrigins,
547
+ allowedOriginPatterns: allowedOriginPatterns.map((pattern) => pattern.source),
523
548
  });
524
549
  const gracefulShutdown = (signal) => {
525
550
  logger.info(`Received ${signal}, shutting down gracefully`);
@@ -545,4 +570,4 @@ const gracefulShutdown = (signal) => {
545
570
  process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
546
571
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
547
572
  //# sourceMappingURL=index.js.map
548
- //# debugId=e28b4205-9ed2-5148-b94c-3326b3073261
573
+ //# debugId=3cfa0c9c-5588-5fbd-a283-4495b71df718
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["index.ts"],"sourceRoot":"/","sourcesContent":["import express from 'express';\nimport type { Request, Response } from 'express';\nimport { createServer } from 'http';\nimport { Server } from 'socket.io';\nimport cors from 'cors';\nimport dotenv from 'dotenv';\nimport { createExpressMiddleware } from '@trpc/server/adapters/express';\nimport { appRouter } from './routers/_app.js';\nimport { createTRPCContext, createCallerFactory } from './trpc.js';\nimport { logger } from './utils/logger.js';\nimport { setupSocketHandlers } from './socket/handlers.js';\nimport { bucket } from './lib/googleCloudStorage.js';\nimport { prisma } from './lib/prisma.js';\nimport { pusher } from './lib/pusher.js';\nimport { connectRedis, disconnectRedis } from './lib/redis.js';\n\nimport { authLimiter, generalLimiter, helmetConfig, uploadLimiter } from './middleware/security.js';\n\nimport * as Sentry from \"@sentry/node\";\nimport { env } from './lib/config/env.js';\nimport compression from 'compression';\nimport { v4 as uuidv4 } from 'uuid';\n\n\nimport \"./instrument.js\";\nimport { openAIClient } from './utils/inference.js';\n\nconst app = express();\n\napp.use(helmetConfig);\napp.use(compression());\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\napp.use((req, res, next) => {\n const requestId = uuidv4();\n res.setHeader('X-Request-ID', requestId);\n next();\n});\n\nconst allowedOrigins = env.NODE_ENV === 'production'\n? [\n 'https://www.studious.sh',\n 'https://studious.sh',\n 'https://dev.studious.sh',\n 'https://www.dev.studious.sh',\n env.NEXT_PUBLIC_APP_URL,\n 'http://localhost:3000',\n\n ].filter(Boolean)\n: [\n 'http://localhost:3000',\n 'http://localhost:3001',\n 'http://127.0.0.1:3000',\n 'http://127.0.0.1:3001',\n\n env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'\n ];\n\n// CORS middleware\napp.use(cors({\n origin: allowedOrigins,\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-user'],\n preflightContinue: false, // Important: stop further handling of OPTIONS\n optionsSuccessStatus: 204, // Recommended for modern browsers\n\n}));\n\napp.use(generalLimiter);\n\n// CORS debugging middleware\napp.use((req, res, next) => {\n if (req.method === 'OPTIONS' || req.path.includes('trpc')) {\n logger.info('CORS Request', {\n method: req.method,\n path: req.path,\n origin: req.headers.origin,\n userAgent: req.headers['user-agent']\n });\n }\n next();\n});\n\n// Response time logging middleware\napp.use((req, res, next) => {\n const start = Date.now();\n res.on('finish', () => {\n const duration = Date.now() - start;\n logger.info('Request completed', {\n method: req.method,\n path: req.path,\n statusCode: res.statusCode,\n duration: `${duration}ms`\n });\n });\n next();\n});\n\n// app.use(\"/panel\", async (_, res) => {\n// if (env.NODE_ENV !== \"development\") {\n// return res.status(404).send(\"Not Found\");\n// }\n\n// // Dynamically import renderTrpcPanel only in development\n// const { renderTrpcPanel } = await import(\"trpc-ui\");\n\n// return res.send(\n// renderTrpcPanel(appRouter, {\n// url: \"/trpc\", // Base url of your trpc server\n// meta: {\n// title: \"Studious Backend\",\n// description:\n// \"This is the backend for the Studious application.\",\n// },\n// })\n// );\n// });\n\n\n// Create HTTP server\nconst httpServer = createServer(app);\n\napp.get('/health', async (req, res) => {\n\n try {\n // Check database connectivity\n await prisma.$queryRaw`SELECT 1`;\n \n res.status(200).json({ \n status: 'OK',\n timestamp: new Date().toISOString(),\n uptime: process.uptime(),\n database: 'connected'\n });\n } catch (error) {\n res.status(503).json({ \n status: 'ERROR',\n database: 'disconnected',\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n});\n\n// Pusher channel auth (for private-* and presence-* channels)\n// Token from: x-user header, or cookie (same-origin requests send cookies automatically)\napp.post('/api/pusher/auth', async (req, res) => {\n try {\n let token = req.headers['x-user'] as string | undefined;\n if (!token && req.headers.cookie) {\n const cookieName = env.PUSHER_AUTH_COOKIE_NAME || 'token';\n const match = req.headers.cookie.match(new RegExp(`${cookieName}=([^;]+)`));\n token = match?.[1]?.trim();\n }\n const { socket_id, channel_name } = req.body as { socket_id?: string; channel_name?: string };\n\n if (!socket_id || !channel_name) {\n return res.status(400).json({ error: 'socket_id and channel_name required' });\n }\n\n if (!token) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const user = await prisma.user.findFirst({\n where: { sessions: { some: { id: token } } },\n select: { id: true, username: true },\n });\n\n if (!user) {\n return res.status(401).json({ error: 'Invalid or expired session' });\n }\n\n // Verify channel access for private-conversation-* channels\n if (channel_name.startsWith('private-conversation-')) {\n const conversationId = channel_name.replace('private-conversation-', '');\n const member = await prisma.conversationMember.findFirst({\n where: { conversationId, userId: user.id },\n });\n \n if (!member) {\n return res.status(403).json({ error: 'Not a member of this conversation' });\n }\n }\n\n if (channel_name.startsWith('private-worksheet-')) {\n const worksheetId = channel_name.replace('private-worksheet-', '');\n const worksheet = await prisma.studentWorksheetResponse.findFirst({\n where: { id: worksheetId, OR: [\n { studentId: user.id },\n { submission: { assignment: { class: { teachers: { some: { id: user.id } } } } } },\n ] },\n });\n if (!worksheet) {\n return res.status(403).json({ error: 'No access to this worksheet' });\n }\n }\n\n if (channel_name.startsWith('private-teacher-')) {\n const classId = channel_name.replace('private-teacher-', '');\n const isTeacher = await prisma.class.findFirst({\n where: { id: classId, teachers: { some: { id: user.id } } },\n });\n if (!isTeacher) {\n return res.status(403).json({ error: 'Not a teacher of this class' });\n }\n }\n\n // Verify channel access for private-class-* channels\n // if (channel_name.startsWith('private-class-')) {\n // const classId = channel_name.replace('private-class-', '');\n // const isMember = await prisma.class.findFirst({\n // where: {\n // id: classId,\n // OR: [\n // { students: { some: { id: user.id } } },\n // { teachers: { some: { id: user.id } } },\n // ],\n // },\n // });\n // if (!isMember) {\n // return res.status(403).json({ error: 'Not a member of this class' });\n // }\n // }\n\n if (channel_name.startsWith('presence-')) {\n const authResponse = pusher.authorizeChannel(socket_id, channel_name, {\n user_id: user.id,\n user_info: { username: user.username },\n });\n return res.json(authResponse);\n }\n\n const authResponse = pusher.authorizeChannel(socket_id, channel_name);\n return res.json(authResponse);\n } catch (error) {\n logger.error('Pusher auth error', { error });\n return res.status(500).json({ error: 'Authentication failed' });\n }\n});\n\n// Setup Socket.IO\nconst io = new Server(httpServer, {\n cors: {\n origin: [\n 'http://localhost:3000', // Frontend development server\n 'http://localhost:3001', // Server port\n 'http://127.0.0.1:3000', // Alternative localhost\n 'http://127.0.0.1:3001', // Alternative localhost\n 'https://www.studious.sh', // Production frontend\n 'https://studious.sh', // Production frontend (without www)\n env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'\n ],\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n credentials: true,\n allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Access-Control-Allow-Origin', 'x-user']\n },\n transports: ['websocket', 'polling'],\n pingTimeout: 60000,\n pingInterval: 25000,\n connectTimeout: 45000,\n path: '/socket.io/',\n allowEIO3: true\n});\n\n// Add server-level logging\nio.engine.on('connection_error', (err: Error) => {\n logger.error('Socket connection error', { error: err.message });\n});\n\n// Setup socket handlers\nsetupSocketHandlers(io);\n\n// File serving endpoint for secure file access\napp.get('/api/files/:fileId', async (req, res) => {\n try {\n const fileId = decodeURIComponent(req.params.fileId);\n // console.log('File request:', { fileId, originalPath: req.params.fileId });\n \n // Get user from request headers\n const userHeader = req.headers['x-user'];\n if (!userHeader) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const token = typeof userHeader === 'string' ? userHeader : userHeader[0];\n\n // Find user by session token\n const user = await prisma.user.findFirst({\n where: {\n sessions: {\n some: {\n id: token\n }\n }\n },\n select: {\n id: true,\n username: true,\n }\n });\n\n if (!user) {\n return res.status(401).json({ error: 'Invalid or expired session' });\n }\n\n // Find file in database by path\n const fileRecord = await prisma.file.findFirst({\n where: { id: fileId },\n include: {\n user: true,\n assignment: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n submission: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true\n }\n }\n }\n }\n }\n },\n annotations: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true\n }\n }\n }\n }\n }\n },\n folder: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n classDraft: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n });\n\n if (!fileRecord) {\n return res.status(404).json({ error: 'File not found in database' });\n }\n\n // Check if user has permission to access this file\n let hasPermission = false;\n\n // Check if user created the file\n if (fileRecord.userId === user.id) {\n hasPermission = true;\n }\n\n // Check if file is related to a class where user is a member\n if (!hasPermission) {\n // Check assignment files\n if (fileRecord.assignment?.class) {\n const classData = fileRecord.assignment.class;\n const isStudent = classData.students.some(student => student.id === user.id);\n const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);\n if (isStudent || isTeacher) {\n hasPermission = true;\n }\n }\n\n if (!hasPermission && fileRecord.annotations) {\n const annotation = fileRecord.annotations;\n if (annotation.studentId === user.id) {\n hasPermission = true;\n } else if (annotation.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {\n hasPermission = true;\n }\n }\n\n // Check submission files (student can access their own submissions, teachers can access all submissions in their class)\n if (!hasPermission && fileRecord.submission) {\n const submission = fileRecord.submission;\n if (submission.studentId === user.id) {\n hasPermission = true; // Student accessing their own submission\n } else if (submission.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {\n hasPermission = true; // Teacher accessing submission in their class\n }\n }\n\n // Check folder files\n if (!hasPermission && fileRecord.folder?.class) {\n const classData = fileRecord.folder.class;\n const isStudent = classData.students.some(student => student.id === user.id);\n const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);\n if (isStudent || isTeacher) {\n hasPermission = true;\n }\n }\n\n // Check class draft files\n if (!hasPermission && fileRecord.classDraft) {\n const classData = fileRecord.classDraft;\n const isStudent = classData.students.some(student => student.id === user.id);\n const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);\n if (isStudent || isTeacher) {\n hasPermission = true;\n }\n }\n }\n\n if (!hasPermission) {\n return res.status(403).json({ error: 'Access denied - insufficient permissions' });\n }\n \n const filePath = fileRecord.path;\n \n // Get file from Google Cloud Storage\n const file = bucket.file(filePath);\n const [exists] = await file.exists();\n \n if (!exists) {\n return res.status(404).json({ error: 'File not found in storage', filePath });\n }\n \n // Get file metadata\n const [metadata] = await file.getMetadata();\n \n // Set appropriate headers\n res.set({\n 'Content-Type': metadata.contentType || 'application/octet-stream',\n 'Content-Length': metadata.size,\n 'Cache-Control': 'public, max-age=31536000', // 1 year cache\n 'ETag': metadata.etag,\n });\n \n // Stream file to response\n const stream = file.createReadStream();\n stream.pipe(res);\n \n stream.on('error', (error) => {\n logger.error('Error streaming file:', {error});\n if (!res.headersSent) {\n res.status(500).json({ error: 'Error streaming file' });\n }\n });\n \n } catch (error) {\n logger.error('Error serving file:', {error});\n res.status(500).json({ error: 'Internal server error' });\n }\n});\n\napp.use('/trpc/auth.login', authLimiter);\napp.use('/trpc/auth.register', authLimiter);\n\n// File upload endpoint for secure file uploads (supports both POST and PUT)\napp.post('/api/upload/:filePath', uploadLimiter, async (req, res) => {\n handleFileUpload(req, res);\n});\n\napp.put('/api/upload/:filePath', uploadLimiter, async (req, res) => {\n handleFileUpload(req, res);\n});\n\nfunction handleFileUpload(req: any, res: any) {\n try {\n const filePath = decodeURIComponent(req.params.filePath);\n \n // Set CORS headers for upload endpoint\n const origin = req.headers.origin;\n const allowedOrigins = [\n 'http://localhost:3000',\n 'http://localhost:3001', \n 'http://127.0.0.1:3000',\n 'http://127.0.0.1:3001',\n 'https://www.studious.sh', // Production frontend\n 'https://studious.sh', // Production frontend (without www)\n env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'\n ];\n \n if (origin && allowedOrigins.includes(origin)) {\n res.header('Access-Control-Allow-Origin', origin);\n } else {\n res.header('Access-Control-Allow-Origin', 'http://localhost:3000');\n }\n \n res.header('Access-Control-Allow-Credentials', 'true');\n \n // Get content type from headers\n const contentType = req.headers['content-type'] || 'application/octet-stream';\n \n // Create a new file in the bucket\n const file = bucket.file(filePath);\n \n // Create a write stream to Google Cloud Storage\n const writeStream = file.createWriteStream({\n metadata: {\n contentType,\n },\n });\n \n // Handle stream events\n writeStream.on('error', (error) => {\n logger.error('Error uploading file:', {error});\n if (!res.headersSent) {\n res.status(500).json({ error: 'Error uploading file' });\n }\n });\n \n writeStream.on('finish', () => {\n res.status(200).json({ \n success: true, \n filePath,\n message: 'File uploaded successfully' \n });\n });\n \n // Pipe the request body to the write stream\n req.pipe(writeStream);\n \n } catch (error) {\n logger.error('Error handling file upload:', {error});\n res.status(500).json({ error: 'Internal server error' });\n }\n}\n\n// Create caller\nconst createCaller = createCallerFactory(appRouter);\n\n// Setup tRPC middleware\napp.use(\n '/trpc',\n createExpressMiddleware({\n router: appRouter,\n createContext: async ({ req, res }: { req: Request; res: Response }) => {\n return createTRPCContext({ req, res });\n },\n })\n);\n\n// IMPORTANT: Sentry error handler must be added AFTER all other middleware and routes\n// but BEFORE any other error handlers\nSentry.setupExpressErrorHandler(app);\n\n// app.use(function onError(err, req, res, next) {\n// // The error id is attached to `res.sentry` to be returned\n// // and optionally displayed to the user for support.\n// res.statusCode = 500;\n// res.end(res.sentry + \"\\n\");\n// });\n\n\nconst PORT = env.PORT || 3001;\n\nconnectRedis().then(() => {\n httpServer.listen(PORT, () => {\n logger.info(`Server running on port ${PORT}`, {\n port: PORT,\n services: ['tRPC', 'Socket.IO', env.REDIS_URL ? 'Redis' : null].filter(Boolean),\n });\n });\n}); \n\n// log all env variables\nlogger.info('Configurations', {\n NODE_ENV: env.NODE_ENV,\n PORT: env.PORT,\n NEXT_PUBLIC_APP_URL: env.NEXT_PUBLIC_APP_URL,\n LOG_MODE: env.LOG_MODE,\n});\n\n// Log CORS configuration\nlogger.info('CORS Configuration', {\n allowedOrigins: [\n 'http://localhost:3000',\n 'http://localhost:3001', \n 'http://127.0.0.1:3000',\n 'http://127.0.0.1:3001',\n env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'\n ]\n});\n\nconst gracefulShutdown = (signal: string) => {\n logger.info(`Received ${signal}, shutting down gracefully`);\n \n httpServer.close(() => {\n logger.info('HTTP server closed');\n \n io.close(() => {\n logger.info('Socket.IO server closed');\n\n disconnectRedis().then(() =>\n prisma.$disconnect().then(() => {\n logger.info('Database connections closed');\n process.exit(0);\n }).catch((err) => {\n logger.error('Error disconnecting from database', { error: err });\n process.exit(1);\n })\n );\n });\n });\n \n // Force shutdown after 10 seconds\n setTimeout(() => {\n logger.error('Forced shutdown after timeout');\n process.exit(1);\n }, 10000);\n};\n\nprocess.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\nprocess.on('SIGINT', () => gracefulShutdown('SIGINT'));"],"names":[],"mappings":"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","debug_id":"e28b4205-9ed2-5148-b94c-3326b3073261"}
1
+ {"version":3,"file":"index.js","sources":["index.ts"],"sourceRoot":"/","sourcesContent":["import express from 'express';\nimport type { Request, Response } from 'express';\nimport { createServer } from 'http';\nimport { Server } from 'socket.io';\nimport cors from 'cors';\nimport dotenv from 'dotenv';\nimport { createExpressMiddleware } from '@trpc/server/adapters/express';\nimport { appRouter } from './routers/_app.js';\nimport { createTRPCContext, createCallerFactory } from './trpc.js';\nimport { findUserBySessionToken } from './models/auth.js';\nimport { logger } from './utils/logger.js';\nimport { setupSocketHandlers } from './socket/handlers.js';\nimport { bucket } from './lib/googleCloudStorage.js';\nimport { validateDirectUploadIntent } from './lib/fileUpload.js';\nimport { prisma } from './lib/prisma.js';\nimport { pusher } from './lib/pusher.js';\nimport { connectRedis, disconnectRedis } from './lib/redis.js';\nimport { createCorsOriginMatcher } from './lib/config/cors.js';\n\nimport { authLimiter, generalLimiter, helmetConfig, uploadLimiter } from './middleware/security.js';\n\nimport * as Sentry from \"@sentry/node\";\nimport { env } from './lib/config/env.js';\nimport compression from 'compression';\nimport { v4 as uuidv4 } from 'uuid';\n\n\nimport \"./instrument.js\";\nimport { openAIClient } from './utils/inference.js';\n\nconst app = express();\n\napp.set('trust proxy', 1);\n\napp.use(helmetConfig);\napp.use(compression());\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\napp.use((req, res, next) => {\n const requestId = uuidv4();\n res.setHeader('X-Request-ID', requestId);\n next();\n});\n\nconst { allowedOrigins, allowedOriginPatterns, isAllowedOrigin } = createCorsOriginMatcher(env);\n\n// CORS middleware\napp.use(cors({\n origin: (origin, callback) => {\n if (!origin || isAllowedOrigin(origin)) {\n callback(null, true);\n return;\n }\n\n logger.warn('CORS origin rejected', { origin, allowedOrigins });\n callback(null, false);\n },\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-user'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n}));\n\napp.use(generalLimiter);\n\n// CORS debugging middleware\napp.use((req, res, next) => {\n if (req.method === 'OPTIONS' || req.path.includes('trpc')) {\n logger.info('CORS Request', {\n method: req.method,\n path: req.path,\n origin: req.headers.origin,\n userAgent: req.headers['user-agent']\n });\n }\n next();\n});\n\n// Response time logging middleware\napp.use((req, res, next) => {\n const start = Date.now();\n res.on('finish', () => {\n const duration = Date.now() - start;\n logger.info('Request completed', {\n method: req.method,\n path: req.path,\n statusCode: res.statusCode,\n duration: `${duration}ms`\n });\n });\n next();\n});\n\n// app.use(\"/panel\", async (_, res) => {\n// if (env.NODE_ENV !== \"development\") {\n// return res.status(404).send(\"Not Found\");\n// }\n\n// // Dynamically import renderTrpcPanel only in development\n// const { renderTrpcPanel } = await import(\"trpc-ui\");\n\n// return res.send(\n// renderTrpcPanel(appRouter, {\n// url: \"/trpc\", // Base url of your trpc server\n// meta: {\n// title: \"Studious Backend\",\n// description:\n// \"This is the backend for the Studious application.\",\n// },\n// })\n// );\n// });\n\n\n// Create HTTP server\nconst httpServer = createServer(app);\n\napp.get('/', async (req, res) => {\n res.status(200).json({ \n status: 'OK',\n timestamp: new Date().toISOString(),\n uptime: process.uptime(),\n version: process.env.npm_package_version,\n environment: process.env.NODE_ENV,\n appUrl: process.env.NEXT_PUBLIC_APP_URL,\n });\n});\n\napp.get('/health', async (req, res) => {\n\n try {\n // Check database connectivity\n await prisma.$queryRaw`SELECT 1`;\n \n res.status(200).json({ \n status: 'OK',\n timestamp: new Date().toISOString(),\n uptime: process.uptime(),\n database: 'connected'\n });\n } catch (error) {\n res.status(503).json({ \n status: 'ERROR',\n database: 'disconnected',\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n }\n});\n\n// Pusher channel auth (for private-* and presence-* channels)\n// Token from: x-user header, or cookie (same-origin requests send cookies automatically)\napp.post('/api/pusher/auth', async (req, res) => {\n try {\n let token = req.headers['x-user'] as string | undefined;\n if (!token && req.headers.cookie) {\n const cookieName = env.PUSHER_AUTH_COOKIE_NAME || 'token';\n const match = req.headers.cookie.match(new RegExp(`${cookieName}=([^;]+)`));\n token = match?.[1]?.trim();\n }\n const { socket_id, channel_name } = req.body as { socket_id?: string; channel_name?: string };\n\n if (!socket_id || !channel_name) {\n return res.status(400).json({ error: 'socket_id and channel_name required' });\n }\n\n if (!token) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const user = await findUserBySessionToken(token);\n\n if (!user) {\n return res.status(401).json({ error: 'Invalid or expired session' });\n }\n\n // Verify channel access for private-conversation-* channels\n if (channel_name.startsWith('private-conversation-')) {\n const conversationId = channel_name.replace('private-conversation-', '');\n const member = await prisma.conversationMember.findFirst({\n where: { conversationId, userId: user.id },\n });\n \n if (!member) {\n return res.status(403).json({ error: 'Not a member of this conversation' });\n }\n }\n\n if (channel_name.startsWith('private-worksheet-')) {\n const worksheetId = channel_name.replace('private-worksheet-', '');\n const worksheet = await prisma.studentWorksheetResponse.findFirst({\n where: { id: worksheetId, OR: [\n { studentId: user.id },\n { submission: { assignment: { class: { teachers: { some: { id: user.id } } } } } },\n ] },\n });\n if (!worksheet) {\n return res.status(403).json({ error: 'No access to this worksheet' });\n }\n }\n\n if (channel_name.startsWith('private-teacher-')) {\n const classId = channel_name.replace('private-teacher-', '');\n const isTeacher = await prisma.class.findFirst({\n where: { id: classId, teachers: { some: { id: user.id } } },\n });\n if (!isTeacher) {\n return res.status(403).json({ error: 'Not a teacher of this class' });\n }\n }\n\n // Verify channel access for private-class-* channels\n // if (channel_name.startsWith('private-class-')) {\n // const classId = channel_name.replace('private-class-', '');\n // const isMember = await prisma.class.findFirst({\n // where: {\n // id: classId,\n // OR: [\n // { students: { some: { id: user.id } } },\n // { teachers: { some: { id: user.id } } },\n // ],\n // },\n // });\n // if (!isMember) {\n // return res.status(403).json({ error: 'Not a member of this class' });\n // }\n // }\n\n if (channel_name.startsWith('presence-')) {\n const authResponse = pusher.authorizeChannel(socket_id, channel_name, {\n user_id: user.id,\n user_info: { username: user.username },\n });\n return res.json(authResponse);\n }\n\n const authResponse = pusher.authorizeChannel(socket_id, channel_name);\n return res.json(authResponse);\n } catch (error) {\n logger.error('Pusher auth error', { error });\n return res.status(500).json({ error: 'Authentication failed' });\n }\n});\n\n// Setup Socket.IO\nconst io = new Server(httpServer, {\n cors: {\n origin: (origin, callback) => {\n callback(null, !origin || isAllowedOrigin(origin));\n },\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n credentials: true,\n allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Access-Control-Allow-Origin', 'x-user']\n },\n transports: ['websocket', 'polling'],\n pingTimeout: 60000,\n pingInterval: 25000,\n connectTimeout: 45000,\n path: '/socket.io/',\n allowEIO3: true\n});\n\n// Add server-level logging\nio.engine.on('connection_error', (err: Error) => {\n logger.error('Socket connection error', { error: err.message });\n});\n\n// Setup socket handlers\nsetupSocketHandlers(io);\n\n// File serving endpoint for secure file access\napp.get('/api/files/:fileId', async (req, res) => {\n try {\n const fileId = decodeURIComponent(req.params.fileId);\n // console.log('File request:', { fileId, originalPath: req.params.fileId });\n \n // Get user from request headers\n const userHeader = req.headers['x-user'];\n if (!userHeader) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const token = typeof userHeader === 'string' ? userHeader : userHeader[0];\n\n // Find user by session token\n const user = await findUserBySessionToken(token);\n\n if (!user) {\n return res.status(401).json({ error: 'Invalid or expired session' });\n }\n\n // Find file in database by path\n const fileRecord = await prisma.file.findFirst({\n where: { id: fileId },\n include: {\n user: true,\n assignment: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n submission: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true\n }\n }\n }\n }\n }\n },\n annotations: {\n include: {\n student: true,\n assignment: {\n include: {\n class: {\n include: {\n teachers: true\n }\n }\n }\n }\n }\n },\n folder: {\n include: {\n class: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n },\n classDraft: {\n include: {\n students: true,\n teachers: true\n }\n }\n }\n });\n\n if (!fileRecord) {\n return res.status(404).json({ error: 'File not found in database' });\n }\n\n // Check if user has permission to access this file\n let hasPermission = false;\n\n // Check if user created the file\n if (fileRecord.userId === user.id) {\n hasPermission = true;\n }\n\n // Check if file is related to a class where user is a member\n if (!hasPermission) {\n // Check assignment files\n if (fileRecord.assignment?.class) {\n const classData = fileRecord.assignment.class;\n const isStudent = classData.students.some(student => student.id === user.id);\n const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);\n if (isStudent || isTeacher) {\n hasPermission = true;\n }\n }\n\n if (!hasPermission && fileRecord.annotations) {\n const annotation = fileRecord.annotations;\n if (annotation.studentId === user.id) {\n hasPermission = true;\n } else if (annotation.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {\n hasPermission = true;\n }\n }\n\n // Check submission files (student can access their own submissions, teachers can access all submissions in their class)\n if (!hasPermission && fileRecord.submission) {\n const submission = fileRecord.submission;\n if (submission.studentId === user.id) {\n hasPermission = true; // Student accessing their own submission\n } else if (submission.assignment?.class?.teachers.some(teacher => teacher.id === user.id)) {\n hasPermission = true; // Teacher accessing submission in their class\n }\n }\n\n // Check folder files\n if (!hasPermission && fileRecord.folder?.class) {\n const classData = fileRecord.folder.class;\n const isStudent = classData.students.some(student => student.id === user.id);\n const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);\n if (isStudent || isTeacher) {\n hasPermission = true;\n }\n }\n\n // Check class draft files\n if (!hasPermission && fileRecord.classDraft) {\n const classData = fileRecord.classDraft;\n const isStudent = classData.students.some(student => student.id === user.id);\n const isTeacher = classData.teachers.some(teacher => teacher.id === user.id);\n if (isStudent || isTeacher) {\n hasPermission = true;\n }\n }\n }\n\n if (!hasPermission) {\n return res.status(403).json({ error: 'Access denied - insufficient permissions' });\n }\n \n const filePath = fileRecord.path;\n \n // Get file from Google Cloud Storage\n const file = bucket.file(filePath);\n const [exists] = await file.exists();\n \n if (!exists) {\n return res.status(404).json({ error: 'File not found in storage', filePath });\n }\n \n // Get file metadata\n const [metadata] = await file.getMetadata();\n \n // Set appropriate headers\n res.set({\n 'Content-Type': metadata.contentType || 'application/octet-stream',\n 'Content-Length': metadata.size,\n 'Cache-Control': 'public, max-age=31536000', // 1 year cache\n 'ETag': metadata.etag,\n });\n \n // Stream file to response\n const stream = file.createReadStream();\n stream.pipe(res);\n \n stream.on('error', (error) => {\n logger.error('Error streaming file:', {error});\n if (!res.headersSent) {\n res.status(500).json({ error: 'Error streaming file' });\n }\n });\n \n } catch (error) {\n logger.error('Error serving file:', {error});\n res.status(500).json({ error: 'Internal server error' });\n }\n});\n\napp.use('/trpc/auth.login', authLimiter);\napp.use('/trpc/auth.register', authLimiter);\n\nasync function getAuthenticatedRequestUser(req: Request) {\n const userHeader = req.headers['x-user'];\n const tokenFromUserHeader = typeof userHeader === 'string' ? userHeader : userHeader?.[0];\n const authorizationHeader = req.headers.authorization;\n const tokenFromAuthorization =\n typeof authorizationHeader === 'string' && authorizationHeader.startsWith('Bearer ')\n ? authorizationHeader.slice(7)\n : null;\n\n let token = tokenFromUserHeader ?? tokenFromAuthorization;\n\n if (!token && req.headers.cookie) {\n const cookieName = env.PUSHER_AUTH_COOKIE_NAME || 'token';\n const match = req.headers.cookie.match(new RegExp(cookieName + '=([^;]+)'));\n token = match?.[1]?.trim() ?? null;\n }\n\n if (!token) {\n return null;\n }\n\n return findUserBySessionToken(token);\n}\n\nfunction getUploadSessionId(req: Request) {\n const queryValue = req.query.uploadSessionId;\n if (typeof queryValue === 'string' && queryValue.trim()) {\n return queryValue;\n }\n\n const headerValue = req.headers['x-upload-session-id'];\n return typeof headerValue === 'string' ? headerValue : headerValue?.[0];\n}\n\n// File upload endpoint for secure file uploads (supports both POST and PUT)\napp.post('/api/upload/:filePath', uploadLimiter, async (req, res) => {\n await handleFileUpload(req, res);\n});\n\napp.put('/api/upload/:filePath', uploadLimiter, async (req, res) => {\n await handleFileUpload(req, res);\n});\n\nasync function handleFileUpload(req: Request, res: Response) {\n try {\n const filePath = decodeURIComponent(req.params.filePath);\n \n // Set CORS headers for upload endpoint\n const origin = req.headers.origin;\n \n if (origin && !isAllowedOrigin(origin)) {\n return res.status(403).json({ error: 'Origin not allowed' });\n }\n\n if (origin) {\n res.header('Access-Control-Allow-Origin', origin);\n }\n \n res.header('Access-Control-Allow-Credentials', 'true');\n\n const user = await getAuthenticatedRequestUser(req);\n if (!user) {\n return res.status(401).json({ error: 'Authentication required' });\n }\n\n const uploadSessionId = getUploadSessionId(req);\n if (!uploadSessionId) {\n return res.status(401).json({ error: 'Upload session is required' });\n }\n\n await validateDirectUploadIntent({\n filePath,\n uploadSessionId,\n userId: user.id,\n });\n \n const contentType = req.headers['content-type'] || 'application/octet-stream';\n const file = bucket.file(filePath);\n const writeStream = file.createWriteStream({\n metadata: {\n contentType,\n },\n });\n \n writeStream.on('error', (error) => {\n logger.error('Error uploading file:', {error, filePath, userId: user.id});\n if (!res.headersSent) {\n res.status(500).json({ error: 'Error uploading file' });\n }\n });\n \n writeStream.on('finish', () => {\n res.status(200).json({ \n success: true, \n filePath,\n message: 'File uploaded successfully' \n });\n });\n \n req.pipe(writeStream);\n \n } catch (error) {\n logger.error('Error handling file upload:', {error});\n const code = typeof error === 'object' && error && 'code' in error ? error.code : null;\n const message = error instanceof Error ? error.message : 'Internal server error';\n\n if (code === 'FORBIDDEN') {\n return res.status(403).json({ error: message });\n }\n\n if (code === 'BAD_REQUEST') {\n return res.status(400).json({ error: message });\n }\n\n res.status(500).json({ error: 'Internal server error' });\n }\n}\n\n// Create caller\nconst createCaller = createCallerFactory(appRouter);\n\n// Fallback OPTIONS handler — prevents tRPC from rejecting preflight requests\n// when the cors middleware passes through without ending the response.\napp.options('/trpc/*', (req, res) => {\n const origin = req.headers.origin;\n if (origin && isAllowedOrigin(origin)) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n res.setHeader('Access-Control-Allow-Credentials', 'true');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, x-user');\n }\n res.sendStatus(204);\n});\n\n// Setup tRPC middleware\napp.use(\n '/trpc',\n createExpressMiddleware({\n router: appRouter,\n createContext: async ({ req, res }: { req: Request; res: Response }) => {\n return createTRPCContext({ req, res });\n },\n })\n);\n\n// IMPORTANT: Sentry error handler must be added AFTER all other middleware and routes\n// but BEFORE any other error handlers\nSentry.setupExpressErrorHandler(app);\n\n// app.use(function onError(err, req, res, next) {\n// // The error id is attached to `res.sentry` to be returned\n// // and optionally displayed to the user for support.\n// res.statusCode = 500;\n// res.end(res.sentry + \"\\n\");\n// });\n\n\nconst PORT = env.PORT || 3001;\n\nconnectRedis().then(() => {\n httpServer.listen(PORT, () => {\n logger.info(`Server running on port ${PORT}`, {\n port: PORT,\n services: ['tRPC', 'Socket.IO', env.REDIS_URL ? 'Redis' : null].filter(Boolean),\n });\n });\n}); \n\n// log all env variables\nlogger.info('Configurations', {\n NODE_ENV: env.NODE_ENV,\n PORT: env.PORT,\n NEXT_PUBLIC_APP_URL: env.NEXT_PUBLIC_APP_URL,\n LOG_MODE: env.LOG_MODE,\n});\n\n// Log CORS configuration\nlogger.info('CORS Configuration', {\n allowedOrigins,\n allowedOriginPatterns: allowedOriginPatterns.map((pattern) => pattern.source),\n});\n\nconst gracefulShutdown = (signal: string) => {\n logger.info(`Received ${signal}, shutting down gracefully`);\n \n httpServer.close(() => {\n logger.info('HTTP server closed');\n \n io.close(() => {\n logger.info('Socket.IO server closed');\n\n disconnectRedis().then(() =>\n prisma.$disconnect().then(() => {\n logger.info('Database connections closed');\n process.exit(0);\n }).catch((err) => {\n logger.error('Error disconnecting from database', { error: err });\n process.exit(1);\n })\n );\n });\n });\n \n // Force shutdown after 10 seconds\n setTimeout(() => {\n logger.error('Forced shutdown after timeout');\n process.exit(1);\n }, 10000);\n};\n\nprocess.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\nprocess.on('SIGINT', () => gracefulShutdown('SIGINT'));\n"],"names":[],"mappings":"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","debug_id":"3cfa0c9c-5588-5fbd-a283-4495b71df718"}
@@ -0,0 +1,16 @@
1
+ type CorsEnv = {
2
+ NODE_ENV?: 'development' | 'production' | 'test';
3
+ NEXT_PUBLIC_APP_URL?: string;
4
+ CORS_ALLOWED_ORIGINS?: string;
5
+ CORS_ALLOWED_ORIGIN_PATTERNS?: string;
6
+ };
7
+ export declare const parseCorsOrigins: (origins?: string) => string[];
8
+ export declare const parseCorsOriginPatterns: (patterns?: string) => RegExp[];
9
+ export declare const isValidCorsOriginPatternList: (patterns?: string) => boolean;
10
+ export declare const createCorsOriginMatcher: (env: CorsEnv) => {
11
+ allowedOrigins: string[];
12
+ allowedOriginPatterns: RegExp[];
13
+ isAllowedOrigin: (origin?: string) => boolean;
14
+ };
15
+ export {};
16
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.d.ts","sourceRoot":"/","sources":["lib/config/cors.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG;IACb,QAAQ,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,MAAM,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4BAA4B,CAAC,EAAE,MAAM,CAAC;CACvC,CAAC;AAqCF,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,aACX,CAAC;AAiBvC,eAAO,MAAM,uBAAuB,GAAI,WAAW,MAAM,aAC6B,CAAC;AAEvF,eAAO,MAAM,4BAA4B,GAAI,WAAW,MAAM,YAO7D,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,KAAK,OAAO;;;+BAShB,MAAM;CAczC,CAAC"}
@@ -0,0 +1,75 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2499986f-b37e-5e23-a023-556de6317b0f")}catch(e){}}();
3
+ const productionOrigins = [
4
+ 'https://www.studious.sh',
5
+ 'https://studious.sh',
6
+ 'https://dev.studious.sh',
7
+ 'https://www.dev.studious.sh',
8
+ ];
9
+ const nonProductionOrigins = [
10
+ 'http://localhost:3000',
11
+ 'http://localhost:3001',
12
+ 'http://127.0.0.1:3000',
13
+ 'http://127.0.0.1:3001',
14
+ ];
15
+ const MAX_PATTERN_LENGTH = 200;
16
+ // This heuristic only targets common nested-quantifier cases like `(a+)+`.
17
+ // It does not catch every risky construct (for example `.*.*` or deep alternation nesting),
18
+ // so `parseCorsOriginPatterns` should still be limited to operator-controlled, anchored patterns.
19
+ const SUSPICIOUS_QUANTIFIER_PATTERN = /(\([^)]*[+*{][^)]*\)|\[[^\]]+[+*{][^\]]*\])[+*{]/;
20
+ const parseList = (value) => value
21
+ ?.split(',')
22
+ .map((item) => item.trim())
23
+ .filter(Boolean) ?? [];
24
+ const isDefined = (value) => Boolean(value);
25
+ const toCorsOrigin = (value) => {
26
+ try {
27
+ return new URL(value).origin;
28
+ }
29
+ catch {
30
+ return value.replace(/\/+$/, '');
31
+ }
32
+ };
33
+ export const parseCorsOrigins = (origins) => parseList(origins).map(toCorsOrigin);
34
+ const validateCorsOriginPattern = (pattern) => {
35
+ if (pattern.length > MAX_PATTERN_LENGTH) {
36
+ throw new Error(`CORS pattern exceeds maximum length of ${MAX_PATTERN_LENGTH}`);
37
+ }
38
+ // Prefer short, anchored patterns and avoid other complex regex features even if they pass this check.
39
+ if (SUSPICIOUS_QUANTIFIER_PATTERN.test(pattern)) {
40
+ throw new Error("CORS pattern contains nested or ambiguous quantifiers");
41
+ }
42
+ return pattern;
43
+ };
44
+ // Patterns are operator-controlled and matched against bounded URL origins, so ReDoS risk is low.
45
+ // Syntax validation does not guarantee safety; prefer anchored patterns and avoid nested quantifiers.
46
+ export const parseCorsOriginPatterns = (patterns) => parseList(patterns).map((pattern) => new RegExp(validateCorsOriginPattern(pattern)));
47
+ export const isValidCorsOriginPatternList = (patterns) => {
48
+ try {
49
+ parseCorsOriginPatterns(patterns);
50
+ return true;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ };
56
+ export const createCorsOriginMatcher = (env) => {
57
+ const allowedOrigins = Array.from(new Set([
58
+ ...(env.NODE_ENV === 'production' ? productionOrigins : nonProductionOrigins),
59
+ env.NEXT_PUBLIC_APP_URL ? toCorsOrigin(env.NEXT_PUBLIC_APP_URL) : undefined,
60
+ ...parseCorsOrigins(env.CORS_ALLOWED_ORIGINS),
61
+ ].filter(isDefined)));
62
+ const allowedOriginPatterns = parseCorsOriginPatterns(env.CORS_ALLOWED_ORIGIN_PATTERNS);
63
+ const isAllowedOrigin = (origin) => {
64
+ const corsOrigin = origin ? toCorsOrigin(origin) : undefined;
65
+ return Boolean(corsOrigin && (allowedOrigins.includes(corsOrigin) ||
66
+ allowedOriginPatterns.some((pattern) => pattern.test(corsOrigin))));
67
+ };
68
+ return {
69
+ allowedOrigins,
70
+ allowedOriginPatterns,
71
+ isAllowedOrigin,
72
+ };
73
+ };
74
+ //# sourceMappingURL=cors.js.map
75
+ //# debugId=2499986f-b37e-5e23-a023-556de6317b0f