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:
Waleed
2025-11-07 15:24:30 -08:00
committed by GitHub
parent c4278266ef
commit bb7016a99f
48 changed files with 1043 additions and 558 deletions

View File

@@ -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')

View File

@@ -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,

View File

@@ -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) {