Skip to content

Commit eca9123

Browse files
authored
improvement(tools): added input validation to jira service management routes (#2642)
1 parent 7356edc commit eca9123

File tree

15 files changed

+247
-6
lines changed

15 files changed

+247
-6
lines changed

apps/sim/app/api/tools/jsm/approvals/route.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import {
4+
validateAlphanumericId,
5+
validateEnum,
6+
validateJiraCloudId,
7+
validateJiraIssueKey,
8+
} from '@/lib/core/security/input-validation'
39
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
410

511
export const dynamic = 'force-dynamic'
612

713
const logger = createLogger('JsmApprovalsAPI')
814

15+
const VALID_ACTIONS = ['get', 'answer'] as const
16+
const VALID_DECISIONS = ['approve', 'decline'] as const
17+
918
export async function POST(request: Request) {
1019
try {
1120
const body = await request.json()
@@ -41,7 +50,23 @@ export async function POST(request: Request) {
4150
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
4251
}
4352

53+
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
54+
if (!actionValidation.isValid) {
55+
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
56+
}
57+
4458
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+
4570
const baseUrl = getJsmApiBaseUrl(cloudId)
4671

4772
if (action === 'get') {
@@ -91,12 +116,14 @@ export async function POST(request: Request) {
91116
return NextResponse.json({ error: 'Approval ID is required' }, { status: 400 })
92117
}
93118

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 })
100127
}
101128

102129
const url = `${baseUrl}/request/${issueIdOrKey}/approval/${approvalId}`

apps/sim/app/api/tools/jsm/comment/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
34
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
45

56
export const dynamic = 'force-dynamic'
@@ -38,6 +39,17 @@ export async function POST(request: Request) {
3839
}
3940

4041
const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken))
42+
43+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
44+
if (!cloudIdValidation.isValid) {
45+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
46+
}
47+
48+
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
49+
if (!issueIdOrKeyValidation.isValid) {
50+
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
51+
}
52+
4153
const baseUrl = getJsmApiBaseUrl(cloudId)
4254

4355
const url = `${baseUrl}/request/${issueIdOrKey}/comment`

apps/sim/app/api/tools/jsm/comments/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
34
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
45

56
export const dynamic = 'force-dynamic'
@@ -36,6 +37,17 @@ export async function POST(request: Request) {
3637
}
3738

3839
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
40+
41+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
42+
if (!cloudIdValidation.isValid) {
43+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
44+
}
45+
46+
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
47+
if (!issueIdOrKeyValidation.isValid) {
48+
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
49+
}
50+
3951
const baseUrl = getJsmApiBaseUrl(cloudId)
4052

4153
const params = new URLSearchParams()

apps/sim/app/api/tools/jsm/customers/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
34
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
45

56
export const dynamic = 'force-dynamic'
@@ -36,6 +37,17 @@ export async function POST(request: Request) {
3637
}
3738

3839
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
40+
41+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
42+
if (!cloudIdValidation.isValid) {
43+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
44+
}
45+
46+
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
47+
if (!serviceDeskIdValidation.isValid) {
48+
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
49+
}
50+
3951
const baseUrl = getJsmApiBaseUrl(cloudId)
4052

4153
const parsedEmails = emails

apps/sim/app/api/tools/jsm/organization/route.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import {
4+
validateAlphanumericId,
5+
validateEnum,
6+
validateJiraCloudId,
7+
} from '@/lib/core/security/input-validation'
38
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
49

510
export const dynamic = 'force-dynamic'
611

712
const logger = createLogger('JsmOrganizationAPI')
813

14+
const VALID_ACTIONS = ['create', 'add_to_service_desk'] as const
15+
916
export async function POST(request: Request) {
1017
try {
1118
const body = await request.json()
@@ -34,7 +41,18 @@ export async function POST(request: Request) {
3441
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
3542
}
3643

44+
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
45+
if (!actionValidation.isValid) {
46+
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
47+
}
48+
3749
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
50+
51+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
52+
if (!cloudIdValidation.isValid) {
53+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
54+
}
55+
3856
const baseUrl = getJsmApiBaseUrl(cloudId)
3957

4058
if (action === 'create') {
@@ -90,6 +108,16 @@ export async function POST(request: Request) {
90108
return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 })
91109
}
92110

111+
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
112+
if (!serviceDeskIdValidation.isValid) {
113+
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
114+
}
115+
116+
const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId')
117+
if (!organizationIdValidation.isValid) {
118+
return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 })
119+
}
120+
93121
const url = `${baseUrl}/servicedesk/${serviceDeskId}/organization`
94122

95123
logger.info('Adding organization to service desk:', { serviceDeskId, organizationId })

apps/sim/app/api/tools/jsm/organizations/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
34
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
45

56
export const dynamic = 'force-dynamic'
@@ -27,6 +28,17 @@ export async function POST(request: Request) {
2728
}
2829

2930
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
31+
32+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
33+
if (!cloudIdValidation.isValid) {
34+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
35+
}
36+
37+
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
38+
if (!serviceDeskIdValidation.isValid) {
39+
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
40+
}
41+
3042
const baseUrl = getJsmApiBaseUrl(cloudId)
3143

3244
const params = new URLSearchParams()

apps/sim/app/api/tools/jsm/participants/route.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import {
4+
validateEnum,
5+
validateJiraCloudId,
6+
validateJiraIssueKey,
7+
} from '@/lib/core/security/input-validation'
38
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
49

510
export const dynamic = 'force-dynamic'
611

712
const logger = createLogger('JsmParticipantsAPI')
813

14+
const VALID_ACTIONS = ['get', 'add'] as const
15+
916
export async function POST(request: Request) {
1017
try {
1118
const body = await request.json()
@@ -40,7 +47,23 @@ export async function POST(request: Request) {
4047
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
4148
}
4249

50+
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
51+
if (!actionValidation.isValid) {
52+
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
53+
}
54+
4355
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
56+
57+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
58+
if (!cloudIdValidation.isValid) {
59+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
60+
}
61+
62+
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
63+
if (!issueIdOrKeyValidation.isValid) {
64+
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
65+
}
66+
4467
const baseUrl = getJsmApiBaseUrl(cloudId)
4568

4669
if (action === 'get') {

apps/sim/app/api/tools/jsm/queues/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
34
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
45

56
export const dynamic = 'force-dynamic'
@@ -35,6 +36,17 @@ export async function POST(request: Request) {
3536
}
3637

3738
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
39+
40+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
41+
if (!cloudIdValidation.isValid) {
42+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
43+
}
44+
45+
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
46+
if (!serviceDeskIdValidation.isValid) {
47+
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
48+
}
49+
3850
const baseUrl = getJsmApiBaseUrl(cloudId)
3951

4052
const params = new URLSearchParams()

apps/sim/app/api/tools/jsm/request/route.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import {
4+
validateAlphanumericId,
5+
validateJiraCloudId,
6+
validateJiraIssueKey,
7+
} from '@/lib/core/security/input-validation'
38
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
49

510
export const dynamic = 'force-dynamic'
@@ -33,11 +38,26 @@ export async function POST(request: Request) {
3338
}
3439

3540
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
41+
42+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
43+
if (!cloudIdValidation.isValid) {
44+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
45+
}
46+
3647
const baseUrl = getJsmApiBaseUrl(cloudId)
3748

3849
const isCreateOperation = serviceDeskId && requestTypeId && summary
3950

4051
if (isCreateOperation) {
52+
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
53+
if (!serviceDeskIdValidation.isValid) {
54+
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
55+
}
56+
57+
const requestTypeIdValidation = validateAlphanumericId(requestTypeId, 'requestTypeId')
58+
if (!requestTypeIdValidation.isValid) {
59+
return NextResponse.json({ error: requestTypeIdValidation.error }, { status: 400 })
60+
}
4161
const url = `${baseUrl}/request`
4262

4363
logger.info('Creating request at:', url)
@@ -95,6 +115,11 @@ export async function POST(request: Request) {
95115
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
96116
}
97117

118+
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
119+
if (!issueIdOrKeyValidation.isValid) {
120+
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
121+
}
122+
98123
const url = `${baseUrl}/request/${issueIdOrKey}`
99124

100125
logger.info('Fetching request from:', url)

apps/sim/app/api/tools/jsm/requests/route.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
3+
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
34
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
45

56
export const dynamic = 'force-dynamic'
@@ -32,6 +33,19 @@ export async function POST(request: Request) {
3233
}
3334

3435
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
36+
37+
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
38+
if (!cloudIdValidation.isValid) {
39+
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
40+
}
41+
42+
if (serviceDeskId) {
43+
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
44+
if (!serviceDeskIdValidation.isValid) {
45+
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
46+
}
47+
}
48+
3549
const baseUrl = getJsmApiBaseUrl(cloudId)
3650

3751
const params = new URLSearchParams()

0 commit comments

Comments
 (0)