Skip to content

Commit c802c8e

Browse files
authored
chore(api): give colliding error helpers domain names, drop VFS pass-through wrappers (#8885)
- v1 admin: unauthorized/forbidden/notFound/badRequest/internalErrorResponse -> admin*Response; errorResponse module-local; conflict/notConfiguredResponse also admin-prefixed - files createErrorResponse -> createFileErrorResponse; workflows createErrorResponse -> createCodedErrorResponse (+ central mock) - import VFS segment codecs from @/lib/vfs/path; delete the mothership pass-throughs and the dead canonicalizeVfsPath - search-replace resource group key uses sortObjectKeysDeep instead of a localeCompare serializer
1 parent 5ae4dcd commit c802c8e

119 files changed

Lines changed: 587 additions & 587 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/app/api/chat/[identifier]/otp/route.test.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const {
7575
const mockGetRedisClient = redisConfigMockFns.mockGetRedisClient
7676
const mockGetEnv = envMockFns.getEnv
7777
const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse
78-
const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse
78+
const mockCreateCodedErrorResponse = workflowsApiUtilsMockFns.mockCreateCodedErrorResponse
7979

8080
vi.mock('@/lib/core/storage', () => ({
8181
getStorageMethod: mockGetStorageMethod,
@@ -196,7 +196,7 @@ describe('Chat OTP API Route', () => {
196196
json: () => Promise.resolve(data),
197197
status: 200,
198198
}))
199-
mockCreateErrorResponse.mockImplementation((message: string, status: number) => ({
199+
mockCreateCodedErrorResponse.mockImplementation((message: string, status: number) => ({
200200
json: () => Promise.resolve({ error: message }),
201201
status,
202202
}))
@@ -230,7 +230,7 @@ describe('Chat OTP API Route', () => {
230230
})
231231

232232
const headerSet = vi.fn()
233-
mockCreateErrorResponse.mockImplementationOnce((message: string, status: number) => ({
233+
mockCreateCodedErrorResponse.mockImplementationOnce((message: string, status: number) => ({
234234
json: () => Promise.resolve({ error: message }),
235235
status,
236236
headers: { set: headerSet },
@@ -334,7 +334,7 @@ describe('Chat OTP API Route', () => {
334334

335335
await PUT(request, createRouteContext({ identifier: mockIdentifier }))
336336

337-
expect(mockCreateErrorResponse).toHaveBeenCalledWith(
337+
expect(mockCreateCodedErrorResponse).toHaveBeenCalledWith(
338338
'This chat does not use email authentication',
339339
400
340340
)
@@ -354,7 +354,7 @@ describe('Chat OTP API Route', () => {
354354

355355
await PUT(request, createRouteContext({ identifier: mockIdentifier }))
356356

357-
expect(mockCreateErrorResponse).toHaveBeenCalledWith('Email not authorized', 403)
357+
expect(mockCreateCodedErrorResponse).toHaveBeenCalledWith('Email not authorized', 403)
358358
expect(mockRedisGet).not.toHaveBeenCalled()
359359
expect(mockSetChatAuthCookie).not.toHaveBeenCalled()
360360
})
@@ -378,7 +378,7 @@ describe('Chat OTP API Route', () => {
378378

379379
await PUT(request, createRouteContext({ identifier: mockIdentifier }))
380380

381-
expect(mockCreateErrorResponse).toHaveBeenCalledWith(
381+
expect(mockCreateCodedErrorResponse).toHaveBeenCalledWith(
382382
'No verification code found, request a new one',
383383
400
384384
)
@@ -410,7 +410,7 @@ describe('Chat OTP API Route', () => {
410410
`otp:${mockEmail}:${mockChatId}`,
411411
5
412412
)
413-
expect(mockCreateErrorResponse).toHaveBeenCalledWith('Invalid verification code', 400)
413+
expect(mockCreateCodedErrorResponse).toHaveBeenCalledWith('Invalid verification code', 400)
414414
})
415415

416416
it('should invalidate OTP and return 429 after max failed attempts', async () => {
@@ -428,7 +428,7 @@ describe('Chat OTP API Route', () => {
428428
await PUT(request, createRouteContext({ identifier: mockIdentifier }))
429429

430430
expect(mockRedisEval).toHaveBeenCalled()
431-
expect(mockCreateErrorResponse).toHaveBeenCalledWith(
431+
expect(mockCreateCodedErrorResponse).toHaveBeenCalledWith(
432432
'Too many failed attempts. Please request a new code.',
433433
429
434434
)

‎apps/sim/app/api/chat/[identifier]/otp/route.ts‎

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { generateRequestId, getClientIp } from '@/lib/core/utils/request'
2626
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
2727
import { sendEmail } from '@/lib/messaging/email/mailer'
2828
import { setChatAuthCookie } from '@/app/api/chat/utils'
29-
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
29+
import { createCodedErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
3030

3131
const logger = createLogger('ChatOtpAPI')
3232

@@ -93,15 +93,18 @@ export const POST = withRouteHandler(
9393
const retryAfter = Math.ceil(
9494
(ipRateLimit.retryAfterMs ?? OTP_IP_RATE_LIMIT.refillIntervalMs) / 1000
9595
)
96-
const response = createErrorResponse('Too many requests. Please try again later.', 429)
96+
const response = createCodedErrorResponse(
97+
'Too many requests. Please try again later.',
98+
429
99+
)
97100
response.headers.set('Retry-After', String(retryAfter))
98101
return response
99102
}
100103
}
101104

102105
const parsed = await parseRequest(requestChatEmailOtpContract, request, context, {
103106
validationErrorResponse: (error) =>
104-
createErrorResponse(getValidationErrorMessage(error, 'Invalid request'), 400),
107+
createCodedErrorResponse(getValidationErrorMessage(error, 'Invalid request'), 400),
105108
})
106109
if (!parsed.success) return parsed.response
107110
const email = normalizeEmail(parsed.data.body.email)
@@ -121,13 +124,13 @@ export const POST = withRouteHandler(
121124

122125
if (deploymentResult.length === 0) {
123126
logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`)
124-
return createErrorResponse('Chat not found', 404)
127+
return createCodedErrorResponse('Chat not found', 404)
125128
}
126129

127130
const deployment = deploymentResult[0]
128131

129132
if (deployment.authType !== 'email') {
130-
return createErrorResponse('This chat does not use email authentication', 400)
133+
return createCodedErrorResponse('This chat does not use email authentication', 400)
131134
}
132135

133136
const allowedEmails: string[] = Array.isArray(deployment.allowedEmails)
@@ -142,7 +145,7 @@ export const POST = withRouteHandler(
142145
return otpRequestAccepted()
143146
} catch (error) {
144147
logger.error(`[${requestId}] Error processing OTP request:`, error)
145-
return createErrorResponse('Failed to process request', 500)
148+
return createCodedErrorResponse('Failed to process request', 500)
146149
}
147150
}
148151
)
@@ -155,7 +158,7 @@ export const PUT = withRouteHandler(
155158
try {
156159
const parsed = await parseRequest(verifyChatEmailOtpContract, request, context, {
157160
validationErrorResponse: (error) =>
158-
createErrorResponse(getValidationErrorMessage(error, 'Invalid request'), 400),
161+
createCodedErrorResponse(getValidationErrorMessage(error, 'Invalid request'), 400),
159162
})
160163
if (!parsed.success) return parsed.response
161164
const { otp } = parsed.data.body
@@ -182,38 +185,41 @@ export const PUT = withRouteHandler(
182185

183186
if (deploymentResult.length === 0) {
184187
logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`)
185-
return createErrorResponse('Chat not found', 404)
188+
return createCodedErrorResponse('Chat not found', 404)
186189
}
187190

188191
const deployment = deploymentResult[0]
189192

190193
if (deployment.authType !== 'email') {
191-
return createErrorResponse('This chat does not use email authentication', 400)
194+
return createCodedErrorResponse('This chat does not use email authentication', 400)
192195
}
193196
if (!isEmailAllowed(email, deployment.allowedEmails)) {
194-
return createErrorResponse('Email not authorized', 403)
197+
return createCodedErrorResponse('Email not authorized', 403)
195198
}
196199

197200
const storedValue = await getOTP('chat', deployment.id, email)
198201
if (!storedValue) {
199-
return createErrorResponse('No verification code found, request a new one', 400)
202+
return createCodedErrorResponse('No verification code found, request a new one', 400)
200203
}
201204

202205
const { otp: storedOTP, attempts } = decodeOTPValue(storedValue)
203206

204207
if (attempts >= MAX_OTP_ATTEMPTS) {
205208
await deleteOTP('chat', deployment.id, email)
206209
logger.warn(`[${requestId}] OTP already locked out for ${email}`)
207-
return createErrorResponse('Too many failed attempts. Please request a new code.', 429)
210+
return createCodedErrorResponse('Too many failed attempts. Please request a new code.', 429)
208211
}
209212

210213
if (storedOTP !== otp) {
211214
const result = await incrementOTPAttempts('chat', deployment.id, email, storedValue)
212215
if (result === 'locked') {
213216
logger.warn(`[${requestId}] OTP invalidated after max failed attempts for ${email}`)
214-
return createErrorResponse('Too many failed attempts. Please request a new code.', 429)
217+
return createCodedErrorResponse(
218+
'Too many failed attempts. Please request a new code.',
219+
429
220+
)
215221
}
216-
return createErrorResponse('Invalid verification code', 400)
222+
return createCodedErrorResponse('Invalid verification code', 400)
217223
}
218224

219225
await deleteOTP('chat', deployment.id, email)
@@ -233,7 +239,7 @@ export const PUT = withRouteHandler(
233239
return response
234240
} catch (error) {
235241
logger.error(`[${requestId}] Error verifying OTP:`, error)
236-
return createErrorResponse('Failed to process request', 500)
242+
return createCodedErrorResponse('Failed to process request', 500)
237243
}
238244
}
239245
)

‎apps/sim/app/api/chat/[identifier]/route.test.ts‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const { mockValidateChatAuth, mockSetChatAuthCookie } = vi.hoisted(() => ({
7676
mockSetChatAuthCookie: vi.fn(),
7777
}))
7878

79-
const mockCreateErrorResponse = workflowsApiUtilsMockFns.mockCreateErrorResponse
79+
const mockCreateCodedErrorResponse = workflowsApiUtilsMockFns.mockCreateCodedErrorResponse
8080
const mockCreateSuccessResponse = workflowsApiUtilsMockFns.mockCreateSuccessResponse
8181

8282
vi.mock('@/app/api/chat/utils', () => ({
@@ -185,15 +185,17 @@ describe('Chat Identifier API Route', () => {
185185
mockEnforceIpRateLimit.mockResolvedValue(null)
186186
mockEnforceResourceRateLimit.mockResolvedValue(null)
187187
mockProcessChatFiles.mockResolvedValue([])
188-
mockCreateErrorResponse.mockImplementation((message: string, status: number, code?: string) => {
189-
return new Response(
190-
JSON.stringify({
191-
error: code || 'Error',
192-
message,
193-
}),
194-
{ status }
195-
)
196-
})
188+
mockCreateCodedErrorResponse.mockImplementation(
189+
(message: string, status: number, code?: string) => {
190+
return new Response(
191+
JSON.stringify({
192+
error: code || 'Error',
193+
message,
194+
}),
195+
{ status }
196+
)
197+
}
198+
)
197199
mockCreateSuccessResponse.mockImplementation((data: unknown) => {
198200
return new Response(JSON.stringify(data), { status: 200 })
199201
})

‎apps/sim/app/api/chat/[identifier]/route.ts‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { LoggingSession } from '@/lib/logs/execution/logging-session'
2222
import { ChatFiles } from '@/lib/uploads'
2323
import { formatOutputSelector } from '@/lib/workflows/streaming/output-selector'
2424
import { setChatAuthCookie, validateChatAuth } from '@/app/api/chat/utils'
25-
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
25+
import { createCodedErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
2626

2727
const logger = createLogger('ChatIdentifierAPI')
2828

@@ -120,9 +120,13 @@ export const POST = withRouteHandler(
120120
maxBodyBytes: CHAT_MAX_REQUEST_BYTES,
121121
validationErrorResponse: (err) => {
122122
const message = err.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')
123-
return createErrorResponse(`Invalid request body: ${message}`, 400, 'VALIDATION_ERROR')
123+
return createCodedErrorResponse(
124+
`Invalid request body: ${message}`,
125+
400,
126+
'VALIDATION_ERROR'
127+
)
124128
},
125-
invalidJsonResponse: () => createErrorResponse('Invalid request body', 400),
129+
invalidJsonResponse: () => createCodedErrorResponse('Invalid request body', 400),
126130
})
127131
if (!parsed.success) return parsed.response
128132
const parsedBody = parsed.data.body
@@ -149,19 +153,19 @@ export const POST = withRouteHandler(
149153

150154
if (deploymentResult.length === 0) {
151155
logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`)
152-
return createErrorResponse('Chat not found', 404)
156+
return createCodedErrorResponse('Chat not found', 404)
153157
}
154158

155159
const deployment = deploymentResult[0]
156160

157161
if (!deployment.isActive) {
158162
logger.warn(`[${requestId}] Chat is not active: ${identifier}`)
159-
return createErrorResponse('This chat is currently unavailable', 403)
163+
return createCodedErrorResponse('This chat is currently unavailable', 403)
160164
}
161165

162166
const authResult = await validateChatAuth(requestId, deployment, request, parsedBody)
163167
if (!authResult.authorized) {
164-
const response = createErrorResponse(
168+
const response = createCodedErrorResponse(
165169
authResult.error || 'Authentication required',
166170
authResult.status || 401
167171
)
@@ -184,7 +188,7 @@ export const POST = withRouteHandler(
184188
}
185189

186190
if (!input && (!files || files.length === 0)) {
187-
return createErrorResponse('No input provided', 400)
191+
return createCodedErrorResponse('No input provided', 400)
188192
}
189193

190194
// Both buckets apply regardless of the chat's auth type: an email or SSO
@@ -228,7 +232,7 @@ export const POST = withRouteHandler(
228232

229233
if (!preprocessResult.success) {
230234
logger.warn(`[${requestId}] Preprocessing failed: ${preprocessResult.error?.message}`)
231-
return createErrorResponse(
235+
return createCodedErrorResponse(
232236
preprocessResult.error?.message || 'Failed to process request',
233237
preprocessResult.error?.statusCode || 500
234238
)
@@ -242,7 +246,7 @@ export const POST = withRouteHandler(
242246
// preprocessExecution reserved a billing concurrency slot; release it on
243247
// this early exit since no LoggingSession will finalize to free it.
244248
await releaseExecutionSlot(executionId)
245-
return createErrorResponse('Workflow has no associated workspace', 500)
249+
return createCodedErrorResponse('Workflow has no associated workspace', 500)
246250
}
247251

248252
try {
@@ -403,11 +407,11 @@ export const POST = withRouteHandler(
403407
// Setup failed before the workflow stream took over slot release;
404408
// free the reserved billing slot (idempotent if already released).
405409
await releaseExecutionSlot(executionId)
406-
return createErrorResponse(error.message || 'Failed to process request', 500)
410+
return createCodedErrorResponse(error.message || 'Failed to process request', 500)
407411
}
408412
} catch (error: any) {
409413
logger.error(`[${requestId}] Error processing chat request:`, error)
410-
return createErrorResponse(error.message || 'Failed to process request', 500)
414+
return createCodedErrorResponse(error.message || 'Failed to process request', 500)
411415
} finally {
412416
ticket.release()
413417
}
@@ -441,28 +445,28 @@ export const GET = withRouteHandler(
441445

442446
if (deploymentResult.length === 0) {
443447
logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`)
444-
return createErrorResponse('Chat not found', 404)
448+
return createCodedErrorResponse('Chat not found', 404)
445449
}
446450

447451
const deployment = deploymentResult[0]
448452

449453
if (!deployment.isActive) {
450454
logger.warn(`[${requestId}] Chat is not active: ${identifier}`)
451-
return createErrorResponse('This chat is currently unavailable', 403)
455+
return createCodedErrorResponse('This chat is currently unavailable', 403)
452456
}
453457

454458
const authResult = await validateChatAuth(requestId, deployment, request)
455459
if (!authResult.authorized) {
456460
logger.info(
457461
`[${requestId}] Authentication required for chat: ${identifier}, type: ${deployment.authType}`
458462
)
459-
return createErrorResponse(authResult.error || 'Authentication required', 401)
463+
return createCodedErrorResponse(authResult.error || 'Authentication required', 401)
460464
}
461465

462466
return createSuccessResponse(toChatConfigResponse(deployment))
463467
} catch (error: any) {
464468
logger.error(`[${requestId}] Error fetching chat info:`, error)
465-
return createErrorResponse(error.message || 'Failed to fetch chat information', 500)
469+
return createCodedErrorResponse(error.message || 'Failed to fetch chat information', 500)
466470
}
467471
}
468472
)

‎apps/sim/app/api/chat/[identifier]/sso/route.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { RateLimiter } from '@/lib/core/rate-limiter'
1010
import { isEmailAllowed } from '@/lib/core/security/deployment'
1111
import { generateRequestId, getClientIp } from '@/lib/core/utils/request'
1212
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
13-
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
13+
import { createCodedErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
1414

1515
const logger = createLogger('ChatSSOAPI')
1616

@@ -32,7 +32,7 @@ const SSO_RESOURCE_RATE_LIMIT: TokenBucketConfig = {
3232
}
3333

3434
function rateLimited(retryAfterMs: number | undefined, fallbackMs: number) {
35-
const response = createErrorResponse('Too many requests. Please try again later.', 429)
35+
const response = createCodedErrorResponse('Too many requests. Please try again later.', 429)
3636
response.headers.set('Retry-After', String(Math.ceil((retryAfterMs ?? fallbackMs) / 1000)))
3737
return response
3838
}
@@ -73,11 +73,11 @@ export const POST = withRouteHandler(
7373

7474
if (!deployment || !deployment.isActive) {
7575
logger.warn(`[${requestId}] SSO check on missing/inactive chat: ${identifier}`)
76-
return createErrorResponse('Chat not found', 404)
76+
return createCodedErrorResponse('Chat not found', 404)
7777
}
7878

7979
if (deployment.authType !== 'sso') {
80-
return createErrorResponse('Chat is not configured for SSO authentication', 400)
80+
return createCodedErrorResponse('Chat is not configured for SSO authentication', 400)
8181
}
8282

8383
const resourceRateLimit = await rateLimiter.checkRateLimitDirect(

0 commit comments

Comments
 (0)