Skip to content

Commit 6ec5cab

Browse files
waleedlatif1claude
andcommitted
fix(jsm): add input validation and extract shared error parser
- Add validateJiraIssueKey for projectIdOrKey in templates and structure routes - Add validateJiraCloudId for formId (UUID) in structure route - Extract parseJsmErrorMessage to shared utils.ts (was duplicated across 3 routes) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a779ea0 commit 6ec5cab

File tree

4 files changed

+60
-47
lines changed

4 files changed

+60
-47
lines changed

apps/sim/app/api/tools/jsm/forms/issue/route.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,17 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { checkInternalAuth } from '@/lib/auth/hybrid'
44
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
5-
import { getJiraCloudId, getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
5+
import {
6+
getJiraCloudId,
7+
getJsmFormsApiBaseUrl,
8+
getJsmHeaders,
9+
parseJsmErrorMessage,
10+
} from '@/tools/jsm/utils'
611

712
export const dynamic = 'force-dynamic'
813

914
const logger = createLogger('JsmIssueFormsAPI')
1015

11-
function parseJsmErrorMessage(status: number, statusText: string, errorText: string): string {
12-
try {
13-
const errorData = JSON.parse(errorText)
14-
if (errorData.errorMessage) {
15-
return `JSM Forms API error: ${errorData.errorMessage}`
16-
}
17-
} catch {
18-
if (errorText) {
19-
return `JSM Forms API error: ${errorText}`
20-
}
21-
}
22-
return `JSM Forms API error: ${status} ${statusText}`
23-
}
24-
2516
export async function POST(request: NextRequest) {
2617
const auth = await checkInternalAuth(request)
2718
if (!auth.success || !auth.userId) {

apps/sim/app/api/tools/jsm/forms/structure/route.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { checkInternalAuth } from '@/lib/auth/hybrid'
4-
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
5-
import { getJiraCloudId, getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
4+
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
5+
import {
6+
getJiraCloudId,
7+
getJsmFormsApiBaseUrl,
8+
getJsmHeaders,
9+
parseJsmErrorMessage,
10+
} from '@/tools/jsm/utils'
611

712
export const dynamic = 'force-dynamic'
813

914
const logger = createLogger('JsmFormStructureAPI')
1015

11-
function parseJsmErrorMessage(status: number, statusText: string, errorText: string): string {
12-
try {
13-
const errorData = JSON.parse(errorText)
14-
if (errorData.errorMessage) {
15-
return `JSM Forms API error: ${errorData.errorMessage}`
16-
}
17-
} catch {
18-
if (errorText) {
19-
return `JSM Forms API error: ${errorText}`
20-
}
21-
}
22-
return `JSM Forms API error: ${status} ${statusText}`
23-
}
24-
2516
export async function POST(request: NextRequest) {
2617
const auth = await checkInternalAuth(request)
2718
if (!auth.success || !auth.userId) {
@@ -59,6 +50,16 @@ export async function POST(request: NextRequest) {
5950
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
6051
}
6152

53+
const projectIdOrKeyValidation = validateJiraIssueKey(projectIdOrKey, 'projectIdOrKey')
54+
if (!projectIdOrKeyValidation.isValid) {
55+
return NextResponse.json({ error: projectIdOrKeyValidation.error }, { status: 400 })
56+
}
57+
58+
const formIdValidation = validateJiraCloudId(formId, 'formId')
59+
if (!formIdValidation.isValid) {
60+
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
61+
}
62+
6263
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
6364
const url = `${baseUrl}/project/${encodeURIComponent(projectIdOrKey)}/form/${encodeURIComponent(formId)}`
6465

apps/sim/app/api/tools/jsm/forms/templates/route.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { checkInternalAuth } from '@/lib/auth/hybrid'
4-
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
5-
import { getJiraCloudId, getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
4+
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
5+
import {
6+
getJiraCloudId,
7+
getJsmFormsApiBaseUrl,
8+
getJsmHeaders,
9+
parseJsmErrorMessage,
10+
} from '@/tools/jsm/utils'
611

712
export const dynamic = 'force-dynamic'
813

914
const logger = createLogger('JsmFormTemplatesAPI')
1015

11-
function parseJsmErrorMessage(status: number, statusText: string, errorText: string): string {
12-
try {
13-
const errorData = JSON.parse(errorText)
14-
if (errorData.errorMessage) {
15-
return `JSM Forms API error: ${errorData.errorMessage}`
16-
}
17-
} catch {
18-
if (errorText) {
19-
return `JSM Forms API error: ${errorText}`
20-
}
21-
}
22-
return `JSM Forms API error: ${status} ${statusText}`
23-
}
24-
2516
export async function POST(request: NextRequest) {
2617
const auth = await checkInternalAuth(request)
2718
if (!auth.success || !auth.userId) {
@@ -54,6 +45,11 @@ export async function POST(request: NextRequest) {
5445
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
5546
}
5647

48+
const projectIdOrKeyValidation = validateJiraIssueKey(projectIdOrKey, 'projectIdOrKey')
49+
if (!projectIdOrKeyValidation.isValid) {
50+
return NextResponse.json({ error: projectIdOrKeyValidation.error }, { status: 400 })
51+
}
52+
5753
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
5854
const url = `${baseUrl}/project/${encodeURIComponent(projectIdOrKey)}/form`
5955

apps/sim/tools/jsm/utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,28 @@ export function getJsmHeaders(accessToken: string): Record<string, string> {
3535
'X-ExperimentalApi': 'opt-in',
3636
}
3737
}
38+
39+
/**
40+
* Parse error messages from JSM/Forms API responses
41+
* @param status - HTTP status code
42+
* @param statusText - HTTP status text
43+
* @param errorText - Raw error response body
44+
* @returns Formatted error message string
45+
*/
46+
export function parseJsmErrorMessage(
47+
status: number,
48+
statusText: string,
49+
errorText: string
50+
): string {
51+
try {
52+
const errorData = JSON.parse(errorText)
53+
if (errorData.errorMessage) {
54+
return `JSM Forms API error: ${errorData.errorMessage}`
55+
}
56+
} catch {
57+
if (errorText) {
58+
return `JSM Forms API error: ${errorText}`
59+
}
60+
}
61+
return `JSM Forms API error: ${status} ${statusText}`
62+
}

0 commit comments

Comments
 (0)