mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-16 01:15:26 -05:00
improvement(routes): type all untyped routes (#1848)
* improvement(routes): type all untyped routes * fix routes, remove unused workspace members route * fix obfuscation of errors behind zod errors * remove extraneous comments
This commit is contained in:
@@ -3,9 +3,9 @@ import { account, user, workflow } from '@sim/db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { OAuthService } from '@/lib/oauth/oauth'
|
||||
import { parseProvider } from '@/lib/oauth/oauth'
|
||||
import { getUserEntityPermissions } from '@/lib/permissions/utils'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
@@ -14,6 +14,17 @@ export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('OAuthCredentialsAPI')
|
||||
|
||||
const credentialsQuerySchema = z
|
||||
.object({
|
||||
provider: z.string().nullish(),
|
||||
workflowId: z.string().uuid('Workflow ID must be a valid UUID').nullish(),
|
||||
credentialId: z.string().uuid('Credential ID must be a valid UUID').nullish(),
|
||||
})
|
||||
.refine((data) => data.provider || data.credentialId, {
|
||||
message: 'Provider or credentialId is required',
|
||||
path: ['provider'],
|
||||
})
|
||||
|
||||
interface GoogleIdToken {
|
||||
email?: string
|
||||
sub?: string
|
||||
@@ -27,11 +38,43 @@ export async function GET(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
// Get query params
|
||||
const { searchParams } = new URL(request.url)
|
||||
const providerParam = searchParams.get('provider') as OAuthService | null
|
||||
const workflowId = searchParams.get('workflowId')
|
||||
const credentialId = searchParams.get('credentialId')
|
||||
const rawQuery = {
|
||||
provider: searchParams.get('provider'),
|
||||
workflowId: searchParams.get('workflowId'),
|
||||
credentialId: searchParams.get('credentialId'),
|
||||
}
|
||||
|
||||
const parseResult = credentialsQuerySchema.safeParse(rawQuery)
|
||||
|
||||
if (!parseResult.success) {
|
||||
const refinementError = parseResult.error.errors.find((err) => err.code === 'custom')
|
||||
if (refinementError) {
|
||||
logger.warn(`[${requestId}] Invalid query parameters: ${refinementError.message}`)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: refinementError.message,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const firstError = parseResult.error.errors[0]
|
||||
const errorMessage = firstError?.message || 'Validation failed'
|
||||
|
||||
logger.warn(`[${requestId}] Invalid query parameters`, {
|
||||
errors: parseResult.error.errors,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: errorMessage,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const { provider: providerParam, workflowId, credentialId } = parseResult.data
|
||||
|
||||
// Authenticate requester (supports session, API key, internal JWT)
|
||||
const authResult = await checkHybridAuth(request)
|
||||
@@ -84,11 +127,6 @@ export async function GET(request: NextRequest) {
|
||||
effectiveUserId = requesterUserId
|
||||
}
|
||||
|
||||
if (!providerParam && !credentialId) {
|
||||
logger.warn(`[${requestId}] Missing provider parameter`)
|
||||
return NextResponse.json({ error: 'Provider or credentialId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Parse the provider to get base provider and feature type (if provider is present)
|
||||
const { baseProvider } = parseProvider(providerParam || 'google-default')
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { db } from '@sim/db'
|
||||
import { account } from '@sim/db/schema'
|
||||
import { and, eq, like, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
@@ -10,6 +11,11 @@ export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('OAuthDisconnectAPI')
|
||||
|
||||
const disconnectSchema = z.object({
|
||||
provider: z.string({ required_error: 'Provider is required' }).min(1, 'Provider is required'),
|
||||
providerId: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Disconnect an OAuth provider for the current user
|
||||
*/
|
||||
@@ -17,23 +23,34 @@ export async function POST(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
// Get the session
|
||||
const session = await getSession()
|
||||
|
||||
// Check if the user is authenticated
|
||||
if (!session?.user?.id) {
|
||||
logger.warn(`[${requestId}] Unauthenticated disconnect request rejected`)
|
||||
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get the provider and providerId from the request body
|
||||
const { provider, providerId } = await request.json()
|
||||
const rawBody = await request.json()
|
||||
const parseResult = disconnectSchema.safeParse(rawBody)
|
||||
|
||||
if (!provider) {
|
||||
logger.warn(`[${requestId}] Missing provider in disconnect request`)
|
||||
return NextResponse.json({ error: 'Provider is required' }, { status: 400 })
|
||||
if (!parseResult.success) {
|
||||
const firstError = parseResult.error.errors[0]
|
||||
const errorMessage = firstError?.message || 'Validation failed'
|
||||
|
||||
logger.warn(`[${requestId}] Invalid disconnect request`, {
|
||||
errors: parseResult.error.errors,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: errorMessage,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const { provider, providerId } = parseResult.data
|
||||
|
||||
logger.info(`[${requestId}] Processing OAuth disconnect request`, {
|
||||
provider,
|
||||
hasProviderId: !!providerId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
@@ -9,6 +10,22 @@ export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('OAuthTokenAPI')
|
||||
|
||||
const tokenRequestSchema = z.object({
|
||||
credentialId: z
|
||||
.string({ required_error: 'Credential ID is required' })
|
||||
.min(1, 'Credential ID is required'),
|
||||
workflowId: z.string().min(1, 'Workflow ID is required').nullish(),
|
||||
})
|
||||
|
||||
const tokenQuerySchema = z.object({
|
||||
credentialId: z
|
||||
.string({
|
||||
required_error: 'Credential ID is required',
|
||||
invalid_type_error: 'Credential ID is required',
|
||||
})
|
||||
.min(1, 'Credential ID is required'),
|
||||
})
|
||||
|
||||
/**
|
||||
* Get an access token for a specific credential
|
||||
* Supports both session-based authentication (for client-side requests)
|
||||
@@ -20,19 +37,31 @@ export async function POST(request: NextRequest) {
|
||||
logger.info(`[${requestId}] OAuth token API POST request received`)
|
||||
|
||||
try {
|
||||
// Parse request body
|
||||
const body = await request.json()
|
||||
const { credentialId, workflowId } = body
|
||||
const rawBody = await request.json()
|
||||
const parseResult = tokenRequestSchema.safeParse(rawBody)
|
||||
|
||||
if (!credentialId) {
|
||||
logger.warn(`[${requestId}] Credential ID is required`)
|
||||
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
|
||||
if (!parseResult.success) {
|
||||
const firstError = parseResult.error.errors[0]
|
||||
const errorMessage = firstError?.message || 'Validation failed'
|
||||
|
||||
logger.warn(`[${requestId}] Invalid token request`, {
|
||||
errors: parseResult.error.errors,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: errorMessage,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const { credentialId, workflowId } = parseResult.data
|
||||
|
||||
// We already have workflowId from the parsed body; avoid forcing hybrid auth to re-read it
|
||||
const authz = await authorizeCredentialUse(request, {
|
||||
credentialId,
|
||||
workflowId,
|
||||
workflowId: workflowId ?? undefined,
|
||||
requireWorkflowIdForInternal: false,
|
||||
})
|
||||
if (!authz.ok || !authz.credentialOwnerUserId) {
|
||||
@@ -63,15 +92,31 @@ export async function GET(request: NextRequest) {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
// Get the credential ID from the query params
|
||||
const { searchParams } = new URL(request.url)
|
||||
const credentialId = searchParams.get('credentialId')
|
||||
|
||||
if (!credentialId) {
|
||||
logger.warn(`[${requestId}] Missing credential ID`)
|
||||
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
|
||||
const rawQuery = {
|
||||
credentialId: searchParams.get('credentialId'),
|
||||
}
|
||||
|
||||
const parseResult = tokenQuerySchema.safeParse(rawQuery)
|
||||
|
||||
if (!parseResult.success) {
|
||||
const firstError = parseResult.error.errors[0]
|
||||
const errorMessage = firstError?.message || 'Validation failed'
|
||||
|
||||
logger.warn(`[${requestId}] Invalid query parameters`, {
|
||||
errors: parseResult.error.errors,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: errorMessage,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const { credentialId } = parseResult.data
|
||||
|
||||
// For GET requests, we only support session-based authentication
|
||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
||||
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
|
||||
|
||||
Reference in New Issue
Block a user