consolidate more code

This commit is contained in:
Vikhyath Mondreti
2026-02-02 17:21:22 -08:00
parent 42767fc4f4
commit 5ecbf6cf4a
6 changed files with 54 additions and 115 deletions

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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)
}