Skip to content
53 changes: 11 additions & 42 deletions apps/sim/app/api/auth/oauth/wealthbox/item/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { wealthboxOAuthItemContract } from '@/lib/api/contracts/selectors/wealthbox'
import { parseRequest } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { validateEnum, validatePathSegment } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

export const dynamic = 'force-dynamic'

Expand All @@ -31,13 +28,6 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
const requestId = generateRequestId()

try {
const session = await getSession()

if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthenticated request rejected`)
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
}

const parsed = await parseRequest(wealthboxOAuthItemContract, request, {})
if (!parsed.success) return parsed.response
const { credentialId, itemId, type } = parsed.data.query
Expand All @@ -60,39 +50,18 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
return NextResponse.json({ error: itemIdValidation.error }, { status: 400 })
}

const resolved = await resolveOAuthAccountId(credentialId)
if (!resolved) {
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
}

if (resolved.workspaceId) {
const { getUserEntityPermissions } = await import('@/lib/workspaces/permissions/utils')
const perm = await getUserEntityPermissions(
session.user.id,
'workspace',
resolved.workspaceId
)
if (perm === null) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
}

const credentials = await db
.select()
.from(account)
.where(eq(account.id, resolved.accountId))
.limit(1)

if (!credentials.length) {
logger.warn(`[${requestId}] Credential not found`, { credentialId })
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
const credAccess = await authorizeCredentialUse(request, {
credentialId,
requireWorkflowIdForInternal: false,
})
if (!credAccess.ok || !credAccess.credentialOwnerUserId) {
logger.warn(`[${requestId}] Credential access denied`, { error: credAccess.error })
return NextResponse.json({ error: credAccess.error || 'Unauthorized' }, { status: 401 })
}

const accountRow = credentials[0]

const accessToken = await refreshAccessTokenIfNeeded(
resolved.accountId,
accountRow.userId,
credentialId,
credAccess.credentialOwnerUserId,
requestId
)

Expand Down
16 changes: 16 additions & 0 deletions apps/sim/app/api/providers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { executeProviderContract } from '@/lib/api/contracts/providers'
import { parseRequest } from '@/lib/api/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
Expand Down Expand Up @@ -141,6 +142,21 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
let finalApiKey: string | undefined = apiKey
try {
if (provider === 'vertex' && vertexCredential) {
const vertexCredAccess = await authorizeCredentialUse(request, {
credentialId: vertexCredential,
workflowId: workflowId || undefined,
requireWorkflowIdForInternal: false,
})
if (!vertexCredAccess.ok) {
logger.warn(`[${requestId}] Vertex credential access denied`, {
error: vertexCredAccess.error,
credentialId: vertexCredential,
})
return NextResponse.json(
{ error: vertexCredAccess.error || 'Unauthorized' },
{ status: 401 }
)
}
finalApiKey = await resolveVertexCredential(requestId, vertexCredential)
}
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const POST = withRouteHandler(
try {
const enqueueResult = await PauseResumeManager.enqueueOrStartResume({
executionId,
workflowId,
contextId,
resumeInput,
userId,
Expand Down Expand Up @@ -249,7 +250,7 @@ export const POST = withRouteHandler(
contextId: enqueueResult.contextId,
failureReason: 'Failed to queue async resume execution',
})
await PauseResumeManager.processQueuedResumes(executionId)
await PauseResumeManager.processQueuedResumes(executionId, workflowId)
return NextResponse.json(
{ error: 'Failed to queue resume execution. Please try again.' },
{ status: 503 }
Expand Down Expand Up @@ -283,15 +284,15 @@ export const POST = withRouteHandler(
executionId: enqueueResult.resumeExecutionId,
message: 'Resume execution started.',
})
} catch (error: any) {
} catch (error) {
logger.error('Resume request failed', {
workflowId,
executionId,
contextId,
error,
})
return NextResponse.json(
{ error: error.message || 'Failed to queue resume request' },
{ error: toError(error).message || 'Failed to queue resume request' },
{ status: 400 }
)
}
Expand Down
1 change: 1 addition & 0 deletions apps/sim/app/api/resume/poll/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ async function dispatchRow(row: DueRow, now: Date): Promise<RowResult> {
try {
const enqueueResult = await PauseResumeManager.enqueueOrStartResume({
executionId: row.executionId,
workflowId: row.workflowId,
contextId: point.contextId,
resumeInput: {},
userId,
Expand Down
81 changes: 16 additions & 65 deletions apps/sim/app/api/tools/gmail/label/route.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { gmailLabelSelectorContract } from '@/lib/api/contracts/selectors/google'
import { parseRequest } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { getScopesForService } from '@/lib/oauth/utils'
import {
getServiceAccountToken,
refreshAccessTokenIfNeeded,
resolveOAuthAccountId,
ServiceAccountTokenError,
} from '@/app/api/auth/oauth/utils'
import { refreshAccessTokenIfNeeded, ServiceAccountTokenError } from '@/app/api/auth/oauth/utils'

export const dynamic = 'force-dynamic'

Expand All @@ -25,13 +17,6 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
const requestId = generateRequestId()

try {
const session = await getSession()

if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthenticated label request rejected`)
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
}

const parsed = await parseRequest(gmailLabelSelectorContract, request, {})
if (!parsed.success) return parsed.response
const { credentialId, labelId } = parsed.data.query
Expand All @@ -43,56 +28,22 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
return NextResponse.json({ error: labelIdValidation.error }, { status: 400 })
}

const resolved = await resolveOAuthAccountId(credentialId)
if (!resolved) {
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
}

if (resolved.workspaceId) {
const { getUserEntityPermissions } = await import('@/lib/workspaces/permissions/utils')
const perm = await getUserEntityPermissions(
session.user.id,
'workspace',
resolved.workspaceId
)
if (perm === null) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const credAccess = await authorizeCredentialUse(request, {
credentialId,
requireWorkflowIdForInternal: false,
})
if (!credAccess.ok || !credAccess.credentialOwnerUserId) {
logger.warn(`[${requestId}] Credential access denied`, { error: credAccess.error })
return NextResponse.json({ error: credAccess.error || 'Unauthorized' }, { status: 401 })
}

let accessToken: string | null = null

if (resolved.credentialType === 'service_account' && resolved.credentialId) {
accessToken = await getServiceAccountToken(
resolved.credentialId,
getScopesForService('gmail'),
impersonateEmail
)
} else {
const credentials = await db
.select()
.from(account)
.where(eq(account.id, resolved.accountId))
.limit(1)

if (!credentials.length) {
logger.warn(`[${requestId}] Credential not found`)
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
}

const accountRow = credentials[0]

logger.info(
`[${requestId}] Using credential: ${accountRow.id}, provider: ${accountRow.providerId}`
)

accessToken = await refreshAccessTokenIfNeeded(
resolved.accountId,
accountRow.userId,
requestId,
getScopesForService('gmail')
)
}
const accessToken = await refreshAccessTokenIfNeeded(
credentialId,
credAccess.credentialOwnerUserId,
requestId,
getScopesForService('gmail'),
impersonateEmail
)

if (!accessToken) {
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
Expand Down
Loading
Loading