mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-03 11:14:58 -05:00
consolidate more code
This commit is contained in:
@@ -2,10 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import {
|
||||
secureFetchWithPinnedIP,
|
||||
validateUrlWithDNS,
|
||||
} from '@/lib/core/security/input-validation.server'
|
||||
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||
@@ -29,22 +26,6 @@ const TeamsWriteChannelSchema = z.object({
|
||||
files: RawFileInputArraySchema.optional().nullable(),
|
||||
})
|
||||
|
||||
async function secureFetchGraph(
|
||||
url: string,
|
||||
options: {
|
||||
method?: string
|
||||
headers?: Record<string, string>
|
||||
body?: string | Buffer | Uint8Array
|
||||
},
|
||||
paramName: string
|
||||
) {
|
||||
const urlValidation = await validateUrlWithDNS(url, paramName)
|
||||
if (!urlValidation.isValid) {
|
||||
throw new Error(urlValidation.error)
|
||||
}
|
||||
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
@@ -123,7 +104,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
logger.info(`[${requestId}] Uploading to Teams: ${uploadUrl}`)
|
||||
|
||||
const uploadResponse = await secureFetchGraph(
|
||||
const uploadResponse = await secureFetchWithValidation(
|
||||
uploadUrl,
|
||||
{
|
||||
method: 'PUT',
|
||||
@@ -154,7 +135,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const fileDetailsUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${uploadedFile.id}?$select=id,name,webDavUrl,eTag,size`
|
||||
|
||||
const fileDetailsResponse = await secureFetchGraph(
|
||||
const fileDetailsResponse = await secureFetchWithValidation(
|
||||
fileDetailsUrl,
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -260,7 +241,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const teamsUrl = `https://graph.microsoft.com/v1.0/teams/${encodeURIComponent(validatedData.teamId)}/channels/${encodeURIComponent(validatedData.channelId)}/messages`
|
||||
|
||||
const teamsResponse = await secureFetchGraph(
|
||||
const teamsResponse = await secureFetchWithValidation(
|
||||
teamsUrl,
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@@ -2,10 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import {
|
||||
secureFetchWithPinnedIP,
|
||||
validateUrlWithDNS,
|
||||
} from '@/lib/core/security/input-validation.server'
|
||||
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||
@@ -28,22 +25,6 @@ const TeamsWriteChatSchema = z.object({
|
||||
files: RawFileInputArraySchema.optional().nullable(),
|
||||
})
|
||||
|
||||
async function secureFetchGraph(
|
||||
url: string,
|
||||
options: {
|
||||
method?: string
|
||||
headers?: Record<string, string>
|
||||
body?: string | Buffer | Uint8Array
|
||||
},
|
||||
paramName: string
|
||||
) {
|
||||
const urlValidation = await validateUrlWithDNS(url, paramName)
|
||||
if (!urlValidation.isValid) {
|
||||
throw new Error(urlValidation.error)
|
||||
}
|
||||
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
@@ -92,6 +73,18 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
for (const file of userFiles) {
|
||||
try {
|
||||
// Microsoft Graph API limits direct uploads to 4MB
|
||||
const maxSize = 4 * 1024 * 1024
|
||||
if (file.size > maxSize) {
|
||||
const sizeMB = (file.size / (1024 * 1024)).toFixed(2)
|
||||
logger.error(
|
||||
`[${requestId}] File ${file.name} is ${sizeMB}MB, exceeds 4MB limit for direct upload`
|
||||
)
|
||||
throw new Error(
|
||||
`File "${file.name}" (${sizeMB}MB) exceeds the 4MB limit for Teams attachments. Use smaller files or upload to SharePoint/OneDrive first.`
|
||||
)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
|
||||
|
||||
const buffer = await downloadFileFromStorage(file, requestId, logger)
|
||||
@@ -109,7 +102,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
logger.info(`[${requestId}] Uploading to Teams: ${uploadUrl}`)
|
||||
|
||||
const uploadResponse = await secureFetchGraph(
|
||||
const uploadResponse = await secureFetchWithValidation(
|
||||
uploadUrl,
|
||||
{
|
||||
method: 'PUT',
|
||||
@@ -140,7 +133,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const fileDetailsUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${uploadedFile.id}?$select=id,name,webDavUrl,eTag,size`
|
||||
|
||||
const fileDetailsResponse = await secureFetchGraph(
|
||||
const fileDetailsResponse = await secureFetchWithValidation(
|
||||
fileDetailsUrl,
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -245,7 +238,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const teamsUrl = `https://graph.microsoft.com/v1.0/chats/${encodeURIComponent(validatedData.chatId)}/messages`
|
||||
|
||||
const teamsResponse = await secureFetchGraph(
|
||||
const teamsResponse = await secureFetchWithValidation(
|
||||
teamsUrl,
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@@ -3,10 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import * as XLSX from 'xlsx'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import {
|
||||
secureFetchWithPinnedIP,
|
||||
validateUrlWithDNS,
|
||||
} from '@/lib/core/security/input-validation.server'
|
||||
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { RawFileInputSchema } from '@/lib/uploads/utils/file-schemas'
|
||||
import {
|
||||
@@ -82,22 +79,6 @@ function validateMicrosoftGraphId(
|
||||
return { isValid: true }
|
||||
}
|
||||
|
||||
async function secureFetchGraph(
|
||||
url: string,
|
||||
options: {
|
||||
method?: string
|
||||
headers?: Record<string, string>
|
||||
body?: string | Buffer | Uint8Array
|
||||
},
|
||||
paramName: string
|
||||
) {
|
||||
const urlValidation = await validateUrlWithDNS(url, paramName)
|
||||
if (!urlValidation.isValid) {
|
||||
throw new Error(urlValidation.error)
|
||||
}
|
||||
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
@@ -231,7 +212,7 @@ export async function POST(request: NextRequest) {
|
||||
uploadUrl += `?@microsoft.graph.conflictBehavior=${validatedData.conflictBehavior}`
|
||||
}
|
||||
|
||||
const uploadResponse = await secureFetchGraph(
|
||||
const uploadResponse = await secureFetchWithValidation(
|
||||
uploadUrl,
|
||||
{
|
||||
method: 'PUT',
|
||||
@@ -268,7 +249,7 @@ export async function POST(request: NextRequest) {
|
||||
const sessionUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/items/${encodeURIComponent(
|
||||
fileData.id
|
||||
)}/workbook/createSession`
|
||||
const sessionResp = await secureFetchGraph(
|
||||
const sessionResp = await secureFetchWithValidation(
|
||||
sessionUrl,
|
||||
{
|
||||
method: 'POST',
|
||||
@@ -291,7 +272,7 @@ export async function POST(request: NextRequest) {
|
||||
const listUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/items/${encodeURIComponent(
|
||||
fileData.id
|
||||
)}/workbook/worksheets?$select=name&$orderby=position&$top=1`
|
||||
const listResp = await secureFetchGraph(
|
||||
const listResp = await secureFetchWithValidation(
|
||||
listUrl,
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -362,7 +343,7 @@ export async function POST(request: NextRequest) {
|
||||
)}')/range(address='${encodeURIComponent(computedRangeAddress)}')`
|
||||
)
|
||||
|
||||
const excelWriteResponse = await secureFetchGraph(
|
||||
const excelWriteResponse = await secureFetchWithValidation(
|
||||
url.toString(),
|
||||
{
|
||||
method: 'PATCH',
|
||||
@@ -406,7 +387,7 @@ export async function POST(request: NextRequest) {
|
||||
const closeUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/items/${encodeURIComponent(
|
||||
fileData.id
|
||||
)}/workbook/closeSession`
|
||||
const closeResp = await secureFetchGraph(
|
||||
const closeResp = await secureFetchWithValidation(
|
||||
closeUrl,
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@@ -2,10 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import {
|
||||
secureFetchWithPinnedIP,
|
||||
validateUrlWithDNS,
|
||||
} from '@/lib/core/security/input-validation.server'
|
||||
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||
@@ -24,22 +21,6 @@ const SharepointUploadSchema = z.object({
|
||||
files: RawFileInputArraySchema.optional().nullable(),
|
||||
})
|
||||
|
||||
async function secureFetchGraph(
|
||||
url: string,
|
||||
options: {
|
||||
method?: string
|
||||
headers?: Record<string, string>
|
||||
body?: string | Buffer | Uint8Array
|
||||
},
|
||||
paramName: string
|
||||
) {
|
||||
const urlValidation = await validateUrlWithDNS(url, paramName)
|
||||
if (!urlValidation.isValid) {
|
||||
throw new Error(urlValidation.error)
|
||||
}
|
||||
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
@@ -101,7 +82,7 @@ export async function POST(request: NextRequest) {
|
||||
if (!effectiveDriveId) {
|
||||
logger.info(`[${requestId}] No driveId provided, fetching default drive for site`)
|
||||
const driveUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drive`
|
||||
const driveResponse = await secureFetchGraph(
|
||||
const driveResponse = await secureFetchWithValidation(
|
||||
driveUrl,
|
||||
{
|
||||
method: 'GET',
|
||||
@@ -171,7 +152,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
logger.info(`[${requestId}] Uploading to: ${uploadUrl}`)
|
||||
|
||||
const uploadResponse = await secureFetchGraph(
|
||||
const uploadResponse = await secureFetchWithValidation(
|
||||
uploadUrl,
|
||||
{
|
||||
method: 'PUT',
|
||||
@@ -192,7 +173,7 @@ export async function POST(request: NextRequest) {
|
||||
// File exists - retry with conflict behavior set to replace
|
||||
logger.warn(`[${requestId}] File ${fileName} already exists, retrying with replace`)
|
||||
const replaceUrl = `${uploadUrl}?@microsoft.graph.conflictBehavior=replace`
|
||||
const replaceResponse = await secureFetchGraph(
|
||||
const replaceResponse = await secureFetchWithValidation(
|
||||
replaceUrl,
|
||||
{
|
||||
method: 'PUT',
|
||||
|
||||
@@ -1,28 +1,9 @@
|
||||
import type { Logger } from '@sim/logger'
|
||||
import {
|
||||
secureFetchWithPinnedIP,
|
||||
validateUrlWithDNS,
|
||||
} from '@/lib/core/security/input-validation.server'
|
||||
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
|
||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||
import type { ToolFileData } from '@/tools/types'
|
||||
|
||||
async function secureFetchExternal(
|
||||
url: string,
|
||||
options: {
|
||||
method?: string
|
||||
headers?: Record<string, string>
|
||||
body?: string | Buffer | Uint8Array
|
||||
},
|
||||
paramName: string
|
||||
) {
|
||||
const urlValidation = await validateUrlWithDNS(url, paramName)
|
||||
if (!urlValidation.isValid) {
|
||||
throw new Error(urlValidation.error)
|
||||
}
|
||||
return secureFetchWithPinnedIP(url, urlValidation.resolvedIP!, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a Slack channel using chat.postMessage
|
||||
*/
|
||||
@@ -128,7 +109,7 @@ export async function uploadFilesToSlack(
|
||||
|
||||
logger.info(`[${requestId}] Got upload URL for ${userFile.name}, file_id: ${urlData.file_id}`)
|
||||
|
||||
const uploadResponse = await secureFetchExternal(
|
||||
const uploadResponse = await secureFetchWithValidation(
|
||||
urlData.upload_url,
|
||||
{
|
||||
method: 'POST',
|
||||
|
||||
@@ -288,3 +288,25 @@ export async function secureFetchWithPinnedIP(
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a URL and performs a secure fetch with DNS pinning in one call.
|
||||
* Combines validateUrlWithDNS and secureFetchWithPinnedIP for convenience.
|
||||
*
|
||||
* @param url - The URL to fetch
|
||||
* @param options - Fetch options (method, headers, body, etc.)
|
||||
* @param paramName - Name of the parameter for error messages (default: 'url')
|
||||
* @returns SecureFetchResponse
|
||||
* @throws Error if URL validation fails
|
||||
*/
|
||||
export async function secureFetchWithValidation(
|
||||
url: string,
|
||||
options: SecureFetchOptions = {},
|
||||
paramName = 'url'
|
||||
): Promise<SecureFetchResponse> {
|
||||
const validation = await validateUrlWithDNS(url, paramName)
|
||||
if (!validation.isValid) {
|
||||
throw new Error(validation.error)
|
||||
}
|
||||
return secureFetchWithPinnedIP(url, validation.resolvedIP!, options)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user