Skip to content

Commit 5ecbf6c

Browse files
committed
consolidate more code
1 parent 42767fc commit 5ecbf6c

File tree

6 files changed

+54
-115
lines changed

6 files changed

+54
-115
lines changed

apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { z } from 'zod'
44
import { checkInternalAuth } from '@/lib/auth/hybrid'
5-
import {
6-
secureFetchWithPinnedIP,
7-
validateUrlWithDNS,
8-
} from '@/lib/core/security/input-validation.server'
5+
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
96
import { generateRequestId } from '@/lib/core/utils/request'
107
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
118
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
@@ -29,22 +26,6 @@ const TeamsWriteChannelSchema = z.object({
2926
files: RawFileInputArraySchema.optional().nullable(),
3027
})
3128

32-
async function secureFetchGraph(
33-
url: string,
34-
options: {
35-
method?: string
36-
headers?: Record<string, string>
37-
body?: string | Buffer | Uint8Array
38-
},
39-
paramName: string
40-
) {
41-
const urlValidation = await validateUrlWithDNS(url, paramName)
42-
if (!urlValidation.isValid) {
43-
throw new Error(urlValidation.error)
44-
}
45-
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
46-
}
47-
4829
export async function POST(request: NextRequest) {
4930
const requestId = generateRequestId()
5031

@@ -123,7 +104,7 @@ export async function POST(request: NextRequest) {
123104

124105
logger.info(`[${requestId}] Uploading to Teams: ${uploadUrl}`)
125106

126-
const uploadResponse = await secureFetchGraph(
107+
const uploadResponse = await secureFetchWithValidation(
127108
uploadUrl,
128109
{
129110
method: 'PUT',
@@ -154,7 +135,7 @@ export async function POST(request: NextRequest) {
154135

155136
const fileDetailsUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${uploadedFile.id}?$select=id,name,webDavUrl,eTag,size`
156137

157-
const fileDetailsResponse = await secureFetchGraph(
138+
const fileDetailsResponse = await secureFetchWithValidation(
158139
fileDetailsUrl,
159140
{
160141
method: 'GET',
@@ -260,7 +241,7 @@ export async function POST(request: NextRequest) {
260241

261242
const teamsUrl = `https://graph.microsoft.com/v1.0/teams/${encodeURIComponent(validatedData.teamId)}/channels/${encodeURIComponent(validatedData.channelId)}/messages`
262243

263-
const teamsResponse = await secureFetchGraph(
244+
const teamsResponse = await secureFetchWithValidation(
264245
teamsUrl,
265246
{
266247
method: 'POST',

apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { z } from 'zod'
44
import { checkInternalAuth } from '@/lib/auth/hybrid'
5-
import {
6-
secureFetchWithPinnedIP,
7-
validateUrlWithDNS,
8-
} from '@/lib/core/security/input-validation.server'
5+
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
96
import { generateRequestId } from '@/lib/core/utils/request'
107
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
118
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
@@ -28,22 +25,6 @@ const TeamsWriteChatSchema = z.object({
2825
files: RawFileInputArraySchema.optional().nullable(),
2926
})
3027

31-
async function secureFetchGraph(
32-
url: string,
33-
options: {
34-
method?: string
35-
headers?: Record<string, string>
36-
body?: string | Buffer | Uint8Array
37-
},
38-
paramName: string
39-
) {
40-
const urlValidation = await validateUrlWithDNS(url, paramName)
41-
if (!urlValidation.isValid) {
42-
throw new Error(urlValidation.error)
43-
}
44-
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
45-
}
46-
4728
export async function POST(request: NextRequest) {
4829
const requestId = generateRequestId()
4930

@@ -92,6 +73,18 @@ export async function POST(request: NextRequest) {
9273

9374
for (const file of userFiles) {
9475
try {
76+
// Microsoft Graph API limits direct uploads to 4MB
77+
const maxSize = 4 * 1024 * 1024
78+
if (file.size > maxSize) {
79+
const sizeMB = (file.size / (1024 * 1024)).toFixed(2)
80+
logger.error(
81+
`[${requestId}] File ${file.name} is ${sizeMB}MB, exceeds 4MB limit for direct upload`
82+
)
83+
throw new Error(
84+
`File "${file.name}" (${sizeMB}MB) exceeds the 4MB limit for Teams attachments. Use smaller files or upload to SharePoint/OneDrive first.`
85+
)
86+
}
87+
9588
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
9689

9790
const buffer = await downloadFileFromStorage(file, requestId, logger)
@@ -109,7 +102,7 @@ export async function POST(request: NextRequest) {
109102

110103
logger.info(`[${requestId}] Uploading to Teams: ${uploadUrl}`)
111104

112-
const uploadResponse = await secureFetchGraph(
105+
const uploadResponse = await secureFetchWithValidation(
113106
uploadUrl,
114107
{
115108
method: 'PUT',
@@ -140,7 +133,7 @@ export async function POST(request: NextRequest) {
140133

141134
const fileDetailsUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${uploadedFile.id}?$select=id,name,webDavUrl,eTag,size`
142135

143-
const fileDetailsResponse = await secureFetchGraph(
136+
const fileDetailsResponse = await secureFetchWithValidation(
144137
fileDetailsUrl,
145138
{
146139
method: 'GET',
@@ -245,7 +238,7 @@ export async function POST(request: NextRequest) {
245238

246239
const teamsUrl = `https://graph.microsoft.com/v1.0/chats/${encodeURIComponent(validatedData.chatId)}/messages`
247240

248-
const teamsResponse = await secureFetchGraph(
241+
const teamsResponse = await secureFetchWithValidation(
249242
teamsUrl,
250243
{
251244
method: 'POST',

apps/sim/app/api/tools/onedrive/upload/route.ts

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
33
import * as XLSX from 'xlsx'
44
import { z } from 'zod'
55
import { checkInternalAuth } from '@/lib/auth/hybrid'
6-
import {
7-
secureFetchWithPinnedIP,
8-
validateUrlWithDNS,
9-
} from '@/lib/core/security/input-validation.server'
6+
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
107
import { generateRequestId } from '@/lib/core/utils/request'
118
import { RawFileInputSchema } from '@/lib/uploads/utils/file-schemas'
129
import {
@@ -82,22 +79,6 @@ function validateMicrosoftGraphId(
8279
return { isValid: true }
8380
}
8481

85-
async function secureFetchGraph(
86-
url: string,
87-
options: {
88-
method?: string
89-
headers?: Record<string, string>
90-
body?: string | Buffer | Uint8Array
91-
},
92-
paramName: string
93-
) {
94-
const urlValidation = await validateUrlWithDNS(url, paramName)
95-
if (!urlValidation.isValid) {
96-
throw new Error(urlValidation.error)
97-
}
98-
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
99-
}
100-
10182
export async function POST(request: NextRequest) {
10283
const requestId = generateRequestId()
10384

@@ -231,7 +212,7 @@ export async function POST(request: NextRequest) {
231212
uploadUrl += `?@microsoft.graph.conflictBehavior=${validatedData.conflictBehavior}`
232213
}
233214

234-
const uploadResponse = await secureFetchGraph(
215+
const uploadResponse = await secureFetchWithValidation(
235216
uploadUrl,
236217
{
237218
method: 'PUT',
@@ -268,7 +249,7 @@ export async function POST(request: NextRequest) {
268249
const sessionUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/items/${encodeURIComponent(
269250
fileData.id
270251
)}/workbook/createSession`
271-
const sessionResp = await secureFetchGraph(
252+
const sessionResp = await secureFetchWithValidation(
272253
sessionUrl,
273254
{
274255
method: 'POST',
@@ -291,7 +272,7 @@ export async function POST(request: NextRequest) {
291272
const listUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/items/${encodeURIComponent(
292273
fileData.id
293274
)}/workbook/worksheets?$select=name&$orderby=position&$top=1`
294-
const listResp = await secureFetchGraph(
275+
const listResp = await secureFetchWithValidation(
295276
listUrl,
296277
{
297278
method: 'GET',
@@ -362,7 +343,7 @@ export async function POST(request: NextRequest) {
362343
)}')/range(address='${encodeURIComponent(computedRangeAddress)}')`
363344
)
364345

365-
const excelWriteResponse = await secureFetchGraph(
346+
const excelWriteResponse = await secureFetchWithValidation(
366347
url.toString(),
367348
{
368349
method: 'PATCH',
@@ -406,7 +387,7 @@ export async function POST(request: NextRequest) {
406387
const closeUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/items/${encodeURIComponent(
407388
fileData.id
408389
)}/workbook/closeSession`
409-
const closeResp = await secureFetchGraph(
390+
const closeResp = await secureFetchWithValidation(
410391
closeUrl,
411392
{
412393
method: 'POST',

apps/sim/app/api/tools/sharepoint/upload/route.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { z } from 'zod'
44
import { checkInternalAuth } from '@/lib/auth/hybrid'
5-
import {
6-
secureFetchWithPinnedIP,
7-
validateUrlWithDNS,
8-
} from '@/lib/core/security/input-validation.server'
5+
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
96
import { generateRequestId } from '@/lib/core/utils/request'
107
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
118
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
@@ -24,22 +21,6 @@ const SharepointUploadSchema = z.object({
2421
files: RawFileInputArraySchema.optional().nullable(),
2522
})
2623

27-
async function secureFetchGraph(
28-
url: string,
29-
options: {
30-
method?: string
31-
headers?: Record<string, string>
32-
body?: string | Buffer | Uint8Array
33-
},
34-
paramName: string
35-
) {
36-
const urlValidation = await validateUrlWithDNS(url, paramName)
37-
if (!urlValidation.isValid) {
38-
throw new Error(urlValidation.error)
39-
}
40-
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
41-
}
42-
4324
export async function POST(request: NextRequest) {
4425
const requestId = generateRequestId()
4526

@@ -101,7 +82,7 @@ export async function POST(request: NextRequest) {
10182
if (!effectiveDriveId) {
10283
logger.info(`[${requestId}] No driveId provided, fetching default drive for site`)
10384
const driveUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drive`
104-
const driveResponse = await secureFetchGraph(
85+
const driveResponse = await secureFetchWithValidation(
10586
driveUrl,
10687
{
10788
method: 'GET',
@@ -171,7 +152,7 @@ export async function POST(request: NextRequest) {
171152

172153
logger.info(`[${requestId}] Uploading to: ${uploadUrl}`)
173154

174-
const uploadResponse = await secureFetchGraph(
155+
const uploadResponse = await secureFetchWithValidation(
175156
uploadUrl,
176157
{
177158
method: 'PUT',
@@ -192,7 +173,7 @@ export async function POST(request: NextRequest) {
192173
// File exists - retry with conflict behavior set to replace
193174
logger.warn(`[${requestId}] File ${fileName} already exists, retrying with replace`)
194175
const replaceUrl = `${uploadUrl}?@microsoft.graph.conflictBehavior=replace`
195-
const replaceResponse = await secureFetchGraph(
176+
const replaceResponse = await secureFetchWithValidation(
196177
replaceUrl,
197178
{
198179
method: 'PUT',

apps/sim/app/api/tools/slack/utils.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,9 @@
11
import type { Logger } from '@sim/logger'
2-
import {
3-
secureFetchWithPinnedIP,
4-
validateUrlWithDNS,
5-
} from '@/lib/core/security/input-validation.server'
2+
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
63
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
74
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
85
import type { ToolFileData } from '@/tools/types'
96

10-
async function secureFetchExternal(
11-
url: string,
12-
options: {
13-
method?: string
14-
headers?: Record<string, string>
15-
body?: string | Buffer | Uint8Array
16-
},
17-
paramName: string
18-
) {
19-
const urlValidation = await validateUrlWithDNS(url, paramName)
20-
if (!urlValidation.isValid) {
21-
throw new Error(urlValidation.error)
22-
}
23-
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
24-
}
25-
267
/**
278
* Sends a message to a Slack channel using chat.postMessage
289
*/
@@ -128,7 +109,7 @@ export async function uploadFilesToSlack(
128109

129110
logger.info(`[${requestId}] Got upload URL for ${userFile.name}, file_id: ${urlData.file_id}`)
130111

131-
const uploadResponse = await secureFetchExternal(
112+
const uploadResponse = await secureFetchWithValidation(
132113
urlData.upload_url,
133114
{
134115
method: 'POST',

apps/sim/lib/core/security/input-validation.server.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,25 @@ export async function secureFetchWithPinnedIP(
288288
req.end()
289289
})
290290
}
291+
292+
/**
293+
* Validates a URL and performs a secure fetch with DNS pinning in one call.
294+
* Combines validateUrlWithDNS and secureFetchWithPinnedIP for convenience.
295+
*
296+
* @param url - The URL to fetch
297+
* @param options - Fetch options (method, headers, body, etc.)
298+
* @param paramName - Name of the parameter for error messages (default: 'url')
299+
* @returns SecureFetchResponse
300+
* @throws Error if URL validation fails
301+
*/
302+
export async function secureFetchWithValidation(
303+
url: string,
304+
options: SecureFetchOptions = {},
305+
paramName = 'url'
306+
): Promise<SecureFetchResponse> {
307+
const validation = await validateUrlWithDNS(url, paramName)
308+
if (!validation.isValid) {
309+
throw new Error(validation.error)
310+
}
311+
return secureFetchWithPinnedIP(url, validation.resolvedIP!, options)
312+
}

0 commit comments

Comments
 (0)