improvement(tools): added input validation to jira service management routes (#2642)

This commit is contained in:
Waleed
2025-12-30 14:55:08 -08:00
committed by GitHub
parent 7356edccbb
commit eca91232bf
15 changed files with 247 additions and 6 deletions

View File

@@ -1,11 +1,20 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import {
validateAlphanumericId,
validateEnum,
validateJiraCloudId,
validateJiraIssueKey,
} from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmApprovalsAPI')
const VALID_ACTIONS = ['get', 'answer'] as const
const VALID_DECISIONS = ['approve', 'decline'] as const
export async function POST(request: Request) {
try {
const body = await request.json()
@@ -41,7 +50,23 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
}
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
if (!actionValidation.isValid) {
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
if (action === 'get') {
@@ -91,12 +116,14 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Approval ID is required' }, { status: 400 })
}
if (!decision || !['approve', 'decline'].includes(decision)) {
logger.error('Invalid or missing decision in request')
return NextResponse.json(
{ error: 'Decision is required and must be "approve" or "decline"' },
{ status: 400 }
)
const approvalIdValidation = validateAlphanumericId(approvalId, 'approvalId')
if (!approvalIdValidation.isValid) {
return NextResponse.json({ error: approvalIdValidation.error }, { status: 400 })
}
const decisionValidation = validateEnum(decision, VALID_DECISIONS, 'decision')
if (!decisionValidation.isValid) {
return NextResponse.json({ error: decisionValidation.error }, { status: 400 })
}
const url = `${baseUrl}/request/${issueIdOrKey}/approval/${approvalId}`

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -38,6 +39,17 @@ export async function POST(request: Request) {
}
const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const url = `${baseUrl}/request/${issueIdOrKey}/comment`

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -36,6 +37,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -36,6 +37,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const parsedEmails = emails

View File

@@ -1,11 +1,18 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import {
validateAlphanumericId,
validateEnum,
validateJiraCloudId,
} from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmOrganizationAPI')
const VALID_ACTIONS = ['create', 'add_to_service_desk'] as const
export async function POST(request: Request) {
try {
const body = await request.json()
@@ -34,7 +41,18 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
}
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
if (!actionValidation.isValid) {
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
if (action === 'create') {
@@ -90,6 +108,16 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 })
}
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId')
if (!organizationIdValidation.isValid) {
return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 })
}
const url = `${baseUrl}/servicedesk/${serviceDeskId}/organization`
logger.info('Adding organization to service desk:', { serviceDeskId, organizationId })

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -27,6 +28,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,11 +1,18 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import {
validateEnum,
validateJiraCloudId,
validateJiraIssueKey,
} from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmParticipantsAPI')
const VALID_ACTIONS = ['get', 'add'] as const
export async function POST(request: Request) {
try {
const body = await request.json()
@@ -40,7 +47,23 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
}
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
if (!actionValidation.isValid) {
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
if (action === 'get') {

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -35,6 +36,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,5 +1,10 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import {
validateAlphanumericId,
validateJiraCloudId,
validateJiraIssueKey,
} from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -33,11 +38,26 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const isCreateOperation = serviceDeskId && requestTypeId && summary
if (isCreateOperation) {
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
const requestTypeIdValidation = validateAlphanumericId(requestTypeId, 'requestTypeId')
if (!requestTypeIdValidation.isValid) {
return NextResponse.json({ error: requestTypeIdValidation.error }, { status: 400 })
}
const url = `${baseUrl}/request`
logger.info('Creating request at:', url)
@@ -95,6 +115,11 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const url = `${baseUrl}/request/${issueIdOrKey}`
logger.info('Fetching request from:', url)

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -32,6 +33,19 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
if (serviceDeskId) {
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -27,6 +28,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
if (!serviceDeskIdValidation.isValid) {
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -22,6 +23,12 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -27,6 +28,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()

View File

@@ -1,5 +1,10 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import {
validateAlphanumericId,
validateJiraCloudId,
validateJiraIssueKey,
} from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -38,6 +43,22 @@ export async function POST(request: Request) {
}
const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const transitionIdValidation = validateAlphanumericId(transitionId, 'transitionId')
if (!transitionIdValidation.isValid) {
return NextResponse.json({ error: transitionIdValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const url = `${baseUrl}/request/${issueIdOrKey}/transition`

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
@@ -27,6 +28,17 @@ export async function POST(request: Request) {
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
if (!issueIdOrKeyValidation.isValid) {
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
}
const baseUrl = getJsmApiBaseUrl(cloudId)
const url = `${baseUrl}/request/${issueIdOrKey}/transition`