|
1 | 1 | import { createLogger } from '@sim/logger' |
2 | 2 | import { NextResponse } from 'next/server' |
| 3 | +import { |
| 4 | + validateAlphanumericId, |
| 5 | + validateEnum, |
| 6 | + validateJiraCloudId, |
| 7 | + validateJiraIssueKey, |
| 8 | +} from '@/lib/core/security/input-validation' |
3 | 9 | import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils' |
4 | 10 |
|
5 | 11 | export const dynamic = 'force-dynamic' |
6 | 12 |
|
7 | 13 | const logger = createLogger('JsmApprovalsAPI') |
8 | 14 |
|
| 15 | +const VALID_ACTIONS = ['get', 'answer'] as const |
| 16 | +const VALID_DECISIONS = ['approve', 'decline'] as const |
| 17 | + |
9 | 18 | export async function POST(request: Request) { |
10 | 19 | try { |
11 | 20 | const body = await request.json() |
@@ -41,7 +50,23 @@ export async function POST(request: Request) { |
41 | 50 | return NextResponse.json({ error: 'Action is required' }, { status: 400 }) |
42 | 51 | } |
43 | 52 |
|
| 53 | + const actionValidation = validateEnum(action, VALID_ACTIONS, 'action') |
| 54 | + if (!actionValidation.isValid) { |
| 55 | + return NextResponse.json({ error: actionValidation.error }, { status: 400 }) |
| 56 | + } |
| 57 | + |
44 | 58 | const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken)) |
| 59 | + |
| 60 | + const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId') |
| 61 | + if (!cloudIdValidation.isValid) { |
| 62 | + return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 }) |
| 63 | + } |
| 64 | + |
| 65 | + const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey') |
| 66 | + if (!issueIdOrKeyValidation.isValid) { |
| 67 | + return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 }) |
| 68 | + } |
| 69 | + |
45 | 70 | const baseUrl = getJsmApiBaseUrl(cloudId) |
46 | 71 |
|
47 | 72 | if (action === 'get') { |
@@ -91,12 +116,14 @@ export async function POST(request: Request) { |
91 | 116 | return NextResponse.json({ error: 'Approval ID is required' }, { status: 400 }) |
92 | 117 | } |
93 | 118 |
|
94 | | - if (!decision || !['approve', 'decline'].includes(decision)) { |
95 | | - logger.error('Invalid or missing decision in request') |
96 | | - return NextResponse.json( |
97 | | - { error: 'Decision is required and must be "approve" or "decline"' }, |
98 | | - { status: 400 } |
99 | | - ) |
| 119 | + const approvalIdValidation = validateAlphanumericId(approvalId, 'approvalId') |
| 120 | + if (!approvalIdValidation.isValid) { |
| 121 | + return NextResponse.json({ error: approvalIdValidation.error }, { status: 400 }) |
| 122 | + } |
| 123 | + |
| 124 | + const decisionValidation = validateEnum(decision, VALID_DECISIONS, 'decision') |
| 125 | + if (!decisionValidation.isValid) { |
| 126 | + return NextResponse.json({ error: decisionValidation.error }, { status: 400 }) |
100 | 127 | } |
101 | 128 |
|
102 | 129 | const url = `${baseUrl}/request/${issueIdOrKey}/approval/${approvalId}` |
|
0 commit comments