Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
665e22c
feat(selectors): add dropdown selectors for 14 integrations
waleedlatif1 Mar 6, 2026
c644a23
fix(selectors): secure OAuth tokens in JSM and Confluence selector ro…
waleedlatif1 Mar 6, 2026
8cf988c
fix(selectors): use sanitized serviceDeskId and encode SharePoint siteId
waleedlatif1 Mar 6, 2026
d55216a
lint
waleedlatif1 Mar 6, 2026
b532a37
fix(selectors): revert encodeURIComponent on SharePoint siteId
waleedlatif1 Mar 6, 2026
a6e695a
fix(selectors): use sanitized cloudId in Confluence and JSM route URLs
waleedlatif1 Mar 6, 2026
c2041af
fix(selectors): add missing context fields to resolution, ensureCrede…
waleedlatif1 Mar 6, 2026
4e30161
fix(selectors): rename advanced subBlock IDs to avoid canonicalParamI…
waleedlatif1 Mar 6, 2026
517d400
Revert "fix(selectors): rename advanced subBlock IDs to avoid canonic…
waleedlatif1 Mar 6, 2026
8512dbd
fix(selectors): rename canonicalParamIds to avoid subBlock ID clashes
waleedlatif1 Mar 6, 2026
4c6c9e8
fix(selectors): rename pre-existing driveId and files canonicalParamI…
waleedlatif1 Mar 6, 2026
a56451a
style: format long lines in calcom, pipedrive, and sharepoint blocks
waleedlatif1 Mar 6, 2026
fe812d3
fix(selectors): resolve cascading context for selected_ canonical par…
waleedlatif1 Mar 6, 2026
20b621d
fix(selectors): replace hacky prefix stripping with explicit CANONICA…
waleedlatif1 Mar 6, 2026
8d231ce
refactor(selectors): remove unnecessary selected_ prefix from canonic…
waleedlatif1 Mar 6, 2026
06cc970
fix(selectors): add spaceId selector pair to Confluence V2 block
waleedlatif1 Mar 6, 2026
613d3e1
refactor(selectors): revert V1 block changes, add selectors to Notion…
waleedlatif1 Mar 6, 2026
1d24842
fix(selectors): audit fixes for auth patterns, registry gaps, and dis…
waleedlatif1 Mar 6, 2026
92e96e8
style: lint formatting fixes
waleedlatif1 Mar 6, 2026
fe34462
fix(selectors): consolidate Notion canonical param pairs into array c…
waleedlatif1 Mar 6, 2026
789b8d7
fix(selectors): add missing selectorKey to Confluence V1 page selector
waleedlatif1 Mar 6, 2026
8ad6660
fix(selectors): use sanitized IDs in URLs, convert SharePoint routes …
waleedlatif1 Mar 6, 2026
b527d23
fix(selectors): revert Zoom meetings type to scheduled for broader co…
waleedlatif1 Mar 6, 2026
ed6feb3
fix(selectors): add SharePoint site ID validator, fix cascading selec…
waleedlatif1 Mar 6, 2026
13c25ec
fix(selectors): hoist requestId before try block in all selector routes
waleedlatif1 Mar 6, 2026
706751a
fix(selectors): hoist requestId before try block in Trello boards route
waleedlatif1 Mar 6, 2026
61d4cbf
fix(selectors): guard selector queries against unresolved variable re…
waleedlatif1 Mar 6, 2026
b2cb783
refactor(selectors): replace hardcoded display name fallbacks with ca…
waleedlatif1 Mar 6, 2026
3c76b47
fix(selectors): tighten SharePoint site ID validation to exclude unde…
waleedlatif1 Mar 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions apps/sim/app/api/tools/airtable/bases/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

const logger = createLogger('AirtableBasesAPI')

export const dynamic = 'force-dynamic'

export async function POST(request: Request) {
const requestId = generateRequestId()
try {
const body = await request.json()
const { credential, workflowId } = body

if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}

const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}

const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{ error: 'Could not retrieve access token', authRequired: true },
{ status: 401 }
)
}

const response = await fetch('https://api.airtable.com/v0/meta/bases', {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
})

if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
logger.error('Failed to fetch Airtable bases', {
status: response.status,
error: errorData,
})
return NextResponse.json(
{ error: 'Failed to fetch Airtable bases', details: errorData },
{ status: response.status }
)
}

const data = await response.json()
const bases = (data.bases || []).map((base: { id: string; name: string }) => ({
id: base.id,
name: base.name,
}))

return NextResponse.json({ bases })
} catch (error) {
logger.error('Error processing Airtable bases request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Airtable bases', details: (error as Error).message },
{ status: 500 }
)
}
}
95 changes: 95 additions & 0 deletions apps/sim/app/api/tools/airtable/tables/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { validateAirtableId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

const logger = createLogger('AirtableTablesAPI')

export const dynamic = 'force-dynamic'

export async function POST(request: Request) {
const requestId = generateRequestId()
try {
const body = await request.json()
const { credential, workflowId, baseId } = body

if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}

if (!baseId) {
logger.error('Missing baseId in request')
return NextResponse.json({ error: 'Base ID is required' }, { status: 400 })
}

const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId')
if (!baseIdValidation.isValid) {
logger.error('Invalid baseId', { error: baseIdValidation.error })
return NextResponse.json({ error: baseIdValidation.error }, { status: 400 })
}

const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}

const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{ error: 'Could not retrieve access token', authRequired: true },
{ status: 401 }
)
}

const response = await fetch(
`https://api.airtable.com/v0/meta/bases/${baseIdValidation.sanitized}/tables`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
)

if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
logger.error('Failed to fetch Airtable tables', {
status: response.status,
error: errorData,
baseId,
})
return NextResponse.json(
{ error: 'Failed to fetch Airtable tables', details: errorData },
{ status: response.status }
)
}

const data = await response.json()
const tables = (data.tables || []).map((table: { id: string; name: string }) => ({
id: table.id,
name: table.name,
}))

return NextResponse.json({ tables })
} catch (error) {
logger.error('Error processing Airtable tables request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Airtable tables', details: (error as Error).message },
{ status: 500 }
)
}
}
79 changes: 79 additions & 0 deletions apps/sim/app/api/tools/asana/workspaces/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

const logger = createLogger('AsanaWorkspacesAPI')

export const dynamic = 'force-dynamic'

export async function POST(request: Request) {
const requestId = generateRequestId()
try {
const body = await request.json()
const { credential, workflowId } = body

if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}

const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}

const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{ error: 'Could not retrieve access token', authRequired: true },
{ status: 401 }
)
}

const response = await fetch('https://app.asana.com/api/1.0/workspaces', {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
},
})

if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
logger.error('Failed to fetch Asana workspaces', {
status: response.status,
error: errorData,
})
return NextResponse.json(
{ error: 'Failed to fetch Asana workspaces', details: errorData },
{ status: response.status }
)
}

const data = await response.json()
const workspaces = (data.data || []).map((workspace: { gid: string; name: string }) => ({
id: workspace.gid,
name: workspace.name,
}))

return NextResponse.json({ workspaces })
} catch (error) {
logger.error('Error processing Asana workspaces request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Asana workspaces', details: (error as Error).message },
{ status: 500 }
)
}
}
79 changes: 79 additions & 0 deletions apps/sim/app/api/tools/attio/lists/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

const logger = createLogger('AttioListsAPI')

export const dynamic = 'force-dynamic'

export async function POST(request: Request) {
const requestId = generateRequestId()
try {
const body = await request.json()
const { credential, workflowId } = body

if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}

const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}

const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{ error: 'Could not retrieve access token', authRequired: true },
{ status: 401 }
)
}

const response = await fetch('https://api.attio.com/v2/lists', {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
},
})

if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
logger.error('Failed to fetch Attio lists', {
status: response.status,
error: errorData,
})
return NextResponse.json(
{ error: 'Failed to fetch Attio lists', details: errorData },
{ status: response.status }
)
}

const data = await response.json()
const lists = (data.data || []).map((list: { api_slug: string; name: string }) => ({
id: list.api_slug,
name: list.name,
}))

return NextResponse.json({ lists })
} catch (error) {
logger.error('Error processing Attio lists request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Attio lists', details: (error as Error).message },
{ status: 500 }
)
}
}
Loading