Skip to content

Commit 8ad6660

Browse files
waleedlatif1claude
andcommitted
fix(selectors): use sanitized IDs in URLs, convert SharePoint routes to POST+authorizeCredentialUse
- Use planIdValidation.sanitized in MS Planner tasks fetch URL - Convert sharepoint/lists and sharepoint/sites from GET+getSession to POST+authorizeCredentialUse - Update registry entries to match POST pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 789b8d7 commit 8ad6660

File tree

4 files changed

+83
-131
lines changed

4 files changed

+83
-131
lines changed

apps/sim/app/api/tools/microsoft_planner/tasks/route.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,14 @@ export async function POST(request: Request) {
5454
)
5555
}
5656

57-
const response = await fetch(`https://graph.microsoft.com/v1.0/planner/plans/${planId}/tasks`, {
58-
headers: {
59-
Authorization: `Bearer ${accessToken}`,
60-
},
61-
})
57+
const response = await fetch(
58+
`https://graph.microsoft.com/v1.0/planner/plans/${planIdValidation.sanitized}/tasks`,
59+
{
60+
headers: {
61+
Authorization: `Bearer ${accessToken}`,
62+
},
63+
}
64+
)
6265

6366
if (!response.ok) {
6467
const errorText = await response.text()

apps/sim/app/api/tools/sharepoint/lists/route.ts

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import { randomUUID } from 'crypto'
2-
import { db } from '@sim/db'
3-
import { account } from '@sim/db/schema'
41
import { createLogger } from '@sim/logger'
5-
import { eq } from 'drizzle-orm'
6-
import { type NextRequest, NextResponse } from 'next/server'
7-
import { getSession } from '@/lib/auth'
8-
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
9-
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
2+
import { NextResponse } from 'next/server'
3+
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
4+
import { generateRequestId } from '@/lib/core/utils/request'
5+
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
106

117
export const dynamic = 'force-dynamic'
128

@@ -22,27 +18,20 @@ interface SharePointList {
2218
}
2319
}
2420

25-
/**
26-
* Get SharePoint lists for a given site from Microsoft Graph API
27-
*/
28-
export async function GET(request: NextRequest) {
29-
const requestId = randomUUID().slice(0, 8)
21+
export async function POST(request: Request) {
22+
const requestId = generateRequestId()
3023

3124
try {
32-
const session = await getSession()
33-
if (!session?.user?.id) {
34-
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
35-
}
36-
37-
const { searchParams } = new URL(request.url)
38-
const credentialId = searchParams.get('credentialId')
39-
const siteId = searchParams.get('siteId')
25+
const body = await request.json()
26+
const { credential, workflowId, siteId } = body
4027

41-
if (!credentialId) {
42-
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
28+
if (!credential) {
29+
logger.error(`[${requestId}] Missing credential in request`)
30+
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
4331
}
4432

4533
if (!siteId) {
34+
logger.error(`[${requestId}] Missing siteId in request`)
4635
return NextResponse.json({ error: 'Site ID is required' }, { status: 400 })
4736
}
4837

@@ -51,47 +40,25 @@ export async function GET(request: NextRequest) {
5140
return NextResponse.json({ error: 'Invalid site ID format' }, { status: 400 })
5241
}
5342

54-
const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255)
55-
if (!credentialIdValidation.isValid) {
56-
logger.warn(`[${requestId}] Invalid credential ID`, { error: credentialIdValidation.error })
57-
return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 })
58-
}
59-
60-
const resolved = await resolveOAuthAccountId(credentialId)
61-
if (!resolved) {
62-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
63-
}
64-
65-
if (resolved.workspaceId) {
66-
const { getUserEntityPermissions } = await import('@/lib/workspaces/permissions/utils')
67-
const perm = await getUserEntityPermissions(
68-
session.user.id,
69-
'workspace',
70-
resolved.workspaceId
71-
)
72-
if (perm === null) {
73-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
74-
}
75-
}
76-
77-
const credentials = await db
78-
.select()
79-
.from(account)
80-
.where(eq(account.id, resolved.accountId))
81-
.limit(1)
82-
if (!credentials.length) {
83-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
43+
const authz = await authorizeCredentialUse(request as any, {
44+
credentialId: credential,
45+
workflowId,
46+
})
47+
if (!authz.ok || !authz.credentialOwnerUserId) {
48+
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
8449
}
8550

86-
const accountRow = credentials[0]
87-
8851
const accessToken = await refreshAccessTokenIfNeeded(
89-
resolved.accountId,
90-
accountRow.userId,
52+
credential,
53+
authz.credentialOwnerUserId,
9154
requestId
9255
)
9356
if (!accessToken) {
94-
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
57+
logger.error(`[${requestId}] Failed to obtain valid access token`)
58+
return NextResponse.json(
59+
{ error: 'Failed to obtain valid access token', authRequired: true },
60+
{ status: 401 }
61+
)
9562
}
9663

9764
const url = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists?$select=id,displayName,description,webUrl&$expand=list($select=hidden)&$top=100`

apps/sim/app/api/tools/sharepoint/sites/route.ts

Lines changed: 24 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,45 @@
1-
import { randomUUID } from 'crypto'
2-
import { db } from '@sim/db'
3-
import { account } from '@sim/db/schema'
41
import { createLogger } from '@sim/logger'
5-
import { eq } from 'drizzle-orm'
6-
import { type NextRequest, NextResponse } from 'next/server'
7-
import { getSession } from '@/lib/auth'
8-
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
9-
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
2+
import { NextResponse } from 'next/server'
3+
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
4+
import { generateRequestId } from '@/lib/core/utils/request'
5+
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
106
import type { SharepointSite } from '@/tools/sharepoint/types'
117

128
export const dynamic = 'force-dynamic'
139

1410
const logger = createLogger('SharePointSitesAPI')
1511

16-
/**
17-
* Get SharePoint sites from Microsoft Graph API
18-
*/
19-
export async function GET(request: NextRequest) {
20-
const requestId = randomUUID().slice(0, 8)
12+
export async function POST(request: Request) {
13+
const requestId = generateRequestId()
2114

2215
try {
23-
const session = await getSession()
24-
if (!session?.user?.id) {
25-
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
26-
}
27-
28-
const { searchParams } = new URL(request.url)
29-
const credentialId = searchParams.get('credentialId')
30-
const query = searchParams.get('query') || ''
31-
32-
if (!credentialId) {
33-
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
34-
}
16+
const body = await request.json()
17+
const { credential, workflowId, query } = body
3518

36-
const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255)
37-
if (!credentialIdValidation.isValid) {
38-
logger.warn(`[${requestId}] Invalid credential ID`, { error: credentialIdValidation.error })
39-
return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 })
19+
if (!credential) {
20+
logger.error(`[${requestId}] Missing credential in request`)
21+
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
4022
}
4123

42-
const resolved = await resolveOAuthAccountId(credentialId)
43-
if (!resolved) {
44-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
45-
}
46-
47-
if (resolved.workspaceId) {
48-
const { getUserEntityPermissions } = await import('@/lib/workspaces/permissions/utils')
49-
const perm = await getUserEntityPermissions(
50-
session.user.id,
51-
'workspace',
52-
resolved.workspaceId
53-
)
54-
if (perm === null) {
55-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
56-
}
57-
}
58-
59-
const credentials = await db
60-
.select()
61-
.from(account)
62-
.where(eq(account.id, resolved.accountId))
63-
.limit(1)
64-
if (!credentials.length) {
65-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
24+
const authz = await authorizeCredentialUse(request as any, {
25+
credentialId: credential,
26+
workflowId,
27+
})
28+
if (!authz.ok || !authz.credentialOwnerUserId) {
29+
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
6630
}
6731

68-
const accountRow = credentials[0]
69-
7032
const accessToken = await refreshAccessTokenIfNeeded(
71-
resolved.accountId,
72-
accountRow.userId,
33+
credential,
34+
authz.credentialOwnerUserId,
7335
requestId
7436
)
7537
if (!accessToken) {
76-
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
38+
logger.error(`[${requestId}] Failed to obtain valid access token`)
39+
return NextResponse.json(
40+
{ error: 'Failed to obtain valid access token', authRequired: true },
41+
{ status: 401 }
42+
)
7743
}
7844

7945
const searchQuery = query || '*'

apps/sim/hooks/selectors/registry.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -727,22 +727,28 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
727727
fetchList: async ({ context }: SelectorQueryArgs) => {
728728
const credentialId = ensureCredential(context, 'sharepoint.lists')
729729
if (!context.siteId) throw new Error('Missing site ID for sharepoint.lists selector')
730+
const body = JSON.stringify({
731+
credential: credentialId,
732+
workflowId: context.workflowId,
733+
siteId: context.siteId,
734+
})
730735
const data = await fetchJson<{ lists: SharepointList[] }>('/api/tools/sharepoint/lists', {
731-
searchParams: {
732-
credentialId,
733-
siteId: context.siteId,
734-
},
736+
method: 'POST',
737+
body,
735738
})
736739
return (data.lists || []).map((list) => ({ id: list.id, label: list.displayName }))
737740
},
738741
fetchById: async ({ context, detailId }: SelectorQueryArgs) => {
739742
if (!detailId || !context.siteId) return null
740743
const credentialId = ensureCredential(context, 'sharepoint.lists')
744+
const body = JSON.stringify({
745+
credential: credentialId,
746+
workflowId: context.workflowId,
747+
siteId: context.siteId,
748+
})
741749
const data = await fetchJson<{ lists: SharepointList[] }>('/api/tools/sharepoint/lists', {
742-
searchParams: {
743-
credentialId,
744-
siteId: context.siteId,
745-
},
750+
method: 'POST',
751+
body,
746752
})
747753
const list = (data.lists || []).find((l) => l.id === detailId) ?? null
748754
if (!list) return null
@@ -1020,10 +1026,15 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
10201026
enabled: ({ context }) => Boolean(context.credentialId),
10211027
fetchList: async ({ context }: SelectorQueryArgs) => {
10221028
const credentialId = ensureCredential(context, 'sharepoint.sites')
1029+
const body = JSON.stringify({
1030+
credential: credentialId,
1031+
workflowId: context.workflowId,
1032+
})
10231033
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
10241034
'/api/tools/sharepoint/sites',
10251035
{
1026-
searchParams: { credentialId },
1036+
method: 'POST',
1037+
body,
10271038
}
10281039
)
10291040
return (data.files || []).map((file) => ({
@@ -1034,10 +1045,15 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
10341045
fetchById: async ({ context, detailId }: SelectorQueryArgs) => {
10351046
if (!detailId) return null
10361047
const credentialId = ensureCredential(context, 'sharepoint.sites')
1048+
const body = JSON.stringify({
1049+
credential: credentialId,
1050+
workflowId: context.workflowId,
1051+
})
10371052
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
10381053
'/api/tools/sharepoint/sites',
10391054
{
1040-
searchParams: { credentialId },
1055+
method: 'POST',
1056+
body,
10411057
}
10421058
)
10431059
const site = (data.files || []).find((f) => f.id === detailId) ?? null

0 commit comments

Comments
 (0)