Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
6 changes: 3 additions & 3 deletions apps/sim/app/api/a2a/serve/[agentId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
isTerminalState,
parseWorkflowSSEChunk,
} from '@/lib/a2a/utils'
import { type AuthResult, checkHybridAuth } from '@/lib/auth/hybrid'
import { type AuthResult, AuthType, checkHybridAuth } from '@/lib/auth/hybrid'
import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redis'
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
import { SSE_HEADERS } from '@/lib/core/utils/sse'
Expand Down Expand Up @@ -242,9 +242,9 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R

const { id, method, params: rpcParams } = body
const requestApiKey = request.headers.get('X-API-Key')
const apiKey = authenticatedAuthType === 'api_key' ? requestApiKey : null
const apiKey = authenticatedAuthType === AuthType.API_KEY ? requestApiKey : null
const isPersonalApiKeyCaller =
authenticatedAuthType === 'api_key' && authenticatedApiKeyType === 'personal'
authenticatedAuthType === AuthType.API_KEY && authenticatedApiKeyType === 'personal'
const billedUserId = await getWorkspaceBilledAccountUserId(agent.workspaceId)
if (!billedUserId) {
logger.error('Unable to resolve workspace billed account for A2A execution', {
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/auth/oauth/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { getCredential, getOAuthToken, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'

Expand Down Expand Up @@ -72,7 +72,7 @@ export async function POST(request: NextRequest) {
})

const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
if (!auth.success || auth.authType !== AuthType.SESSION || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized request for credentialAccountUserId path`, {
success: auth.success,
authType: auth.authType,
Expand Down Expand Up @@ -202,7 +202,7 @@ export async function GET(request: NextRequest) {
credentialId,
requireWorkflowIdForInternal: false,
})
if (!authz.ok || authz.authType !== 'session' || !authz.credentialOwnerUserId) {
if (!authz.ok || authz.authType !== AuthType.SESSION || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}

Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/knowledge/[id]/tag-definitions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants'
import { createTagDefinition, getTagDefinitions } from '@/lib/knowledge/tags/service'
import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils'
Expand All @@ -25,7 +25,7 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
}

// For session auth, verify KB access. Internal JWT is trusted.
if (auth.authType === 'session' && auth.userId) {
if (auth.authType === AuthType.SESSION && auth.userId) {
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, auth.userId)
if (!accessCheck.hasAccess) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
Expand Down Expand Up @@ -62,7 +62,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
}

// For session auth, verify KB access. Internal JWT is trusted.
if (auth.authType === 'session' && auth.userId) {
if (auth.authType === AuthType.SESSION && auth.userId) {
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, auth.userId)
if (!accessCheck.hasAccess) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/mcp/serve/[serverId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { workflow, workflowMcpServer, workflowMcpTool } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { type AuthResult, checkHybridAuth } from '@/lib/auth/hybrid'
import { type AuthResult, AuthType, checkHybridAuth } from '@/lib/auth/hybrid'
import { generateInternalToken } from '@/lib/auth/internal'
import { getMaxExecutionTimeout } from '@/lib/core/execution-limits'
import { getInternalApiBaseUrl } from '@/lib/core/utils/urls'
Expand Down Expand Up @@ -137,7 +137,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
executeAuthContext = {
authType: auth.authType,
userId: auth.userId,
apiKey: auth.authType === 'api_key' ? request.headers.get('X-API-Key') : null,
apiKey: auth.authType === AuthType.API_KEY ? request.headers.get('X-API-Key') : null,
}
}

Expand Down Expand Up @@ -295,7 +295,7 @@ async function handleToolsCall(
const internalToken = await generateInternalToken(publicServerOwnerId)
headers.Authorization = `Bearer ${internalToken}`
} else if (executeAuthContext) {
if (executeAuthContext.authType === 'api_key' && executeAuthContext.apiKey) {
if (executeAuthContext.authType === AuthType.API_KEY && executeAuthContext.apiKey) {
headers['X-API-Key'] = executeAuthContext.apiKey
} else {
const internalToken = await generateInternalToken(executeAuthContext.userId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { AuthType } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { preprocessExecution } from '@/lib/execution/preprocessing'
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
Expand Down Expand Up @@ -39,7 +40,7 @@ export async function POST(

const resumeInput = payload?.input ?? payload ?? {}
const isPersonalApiKeyCaller =
access.auth?.authType === 'api_key' && access.auth?.apiKeyType === 'personal'
access.auth?.authType === AuthType.API_KEY && access.auth?.apiKeyType === 'personal'

let userId: string
if (isPersonalApiKeyCaller && access.auth?.userId) {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/users/me/usage-limits/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { AuthType, checkHybridAuth } from '@/lib/auth/hybrid'
import { checkServerSideUsageLimits } from '@/lib/billing'
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { getEffectiveCurrentPeriodCost } from '@/lib/billing/core/usage'
Expand All @@ -20,7 +20,7 @@ export async function GET(request: NextRequest) {

const userSubscription = await getHighestPrioritySubscription(authenticatedUserId)
const rateLimiter = new RateLimiter()
const triggerType = auth.authType === 'api_key' ? 'api' : 'manual'
const triggerType = auth.authType === AuthType.API_KEY ? 'api' : 'manual'
const [syncStatus, asyncStatus] = await Promise.all([
rateLimiter.getRateLimitStatusWithSubscription(
authenticatedUserId,
Expand Down
16 changes: 9 additions & 7 deletions apps/sim/app/api/workflows/[id]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { validate as uuidValidate, v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { AuthType, checkHybridAuth } from '@/lib/auth/hybrid'
import { getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs'
import {
createTimeoutAbortController,
Expand Down Expand Up @@ -322,7 +322,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
)
}

const defaultTriggerType = isPublicApiAccess || auth.authType === 'api_key' ? 'api' : 'manual'
const defaultTriggerType =
isPublicApiAccess || auth.authType === AuthType.API_KEY ? 'api' : 'manual'

const {
selectedOutputs,
Expand Down Expand Up @@ -381,7 +382,9 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
// For API key and internal JWT auth, the entire body is the input (except for our control fields)
// For session auth, the input is explicitly provided in the input field
const input =
isPublicApiAccess || auth.authType === 'api_key' || auth.authType === 'internal_jwt'
isPublicApiAccess ||
auth.authType === AuthType.API_KEY ||
auth.authType === AuthType.INTERNAL_JWT
? (() => {
const {
selectedOutputs,
Expand All @@ -407,7 +410,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
// Public API callers always execute the deployed state, never the draft.
const shouldUseDraftState = isPublicApiAccess
? false
: (useDraftState ?? auth.authType === 'session')
: (useDraftState ?? auth.authType === AuthType.SESSION)
const streamHeader = req.headers.get('X-Stream-Response') === 'true'
const enableSSE = streamHeader || streamParam === true
const executionModeHeader = req.headers.get('X-Execution-Mode')
Expand Down Expand Up @@ -440,7 +443,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
// Client-side sessions and personal API keys bill/permission-check the
// authenticated user, not the workspace billed account.
const useAuthenticatedUserAsActor =
isClientSession || (auth.authType === 'api_key' && auth.apiKeyType === 'personal')
isClientSession || (auth.authType === AuthType.API_KEY && auth.apiKeyType === 'personal')

// Authorization fetches the full workflow record and checks workspace permissions.
// Run it first so we can pass the record to preprocessing (eliminates a duplicate DB query).
Expand Down Expand Up @@ -670,8 +673,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:

const resultWithBase64 = { ...result, output: outputWithBase64 }

const hasResponseBlock = workflowHasResponseBlock(resultWithBase64)
if (hasResponseBlock) {
if (auth.authType !== AuthType.INTERNAL_JWT && workflowHasResponseBlock(resultWithBase64)) {
return createHttpResponseFromBlock(resultWithBase64)
}

Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/workflows/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { and, eq, isNull, ne } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkHybridAuth, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { AuthType, checkHybridAuth, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { env } from '@/lib/core/config/env'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
Expand Down Expand Up @@ -39,7 +39,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const isInternalCall = auth.authType === 'internal_jwt'
const isInternalCall = auth.authType === AuthType.INTERNAL_JWT
const userId = auth.userId || null

let workflowData = await getWorkflowById(workflowId)
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/lib/auth/credential-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { db } from '@sim/db'
import { account, credential, credentialMember, workflow as workflowTable } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'

export interface CredentialAccessResult {
ok: boolean
error?: string
authType?: 'session' | 'internal_jwt'
authType?: typeof AuthType.SESSION | typeof AuthType.INTERNAL_JWT
requesterUserId?: string
credentialOwnerUserId?: string
workspaceId?: string
Expand Down Expand Up @@ -39,7 +39,7 @@ export async function authorizeCredentialUse(
return { ok: false, error: auth.error || 'Authentication required' }
}

const actingUserId = auth.authType === 'internal_jwt' ? callerUserId : auth.userId
const actingUserId = auth.authType === AuthType.INTERNAL_JWT ? callerUserId : auth.userId

const [workflowContext] = workflowId
? await db
Expand Down Expand Up @@ -217,7 +217,7 @@ export async function authorizeCredentialUse(
return { ok: false, error: 'Credential not found' }
}

if (auth.authType === 'internal_jwt') {
if (auth.authType === AuthType.INTERNAL_JWT) {
return { ok: false, error: 'workflowId is required' }
}

Expand Down
20 changes: 14 additions & 6 deletions apps/sim/lib/auth/hybrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ import { verifyInternalToken } from '@/lib/auth/internal'

const logger = createLogger('HybridAuth')

export const AuthType = {
SESSION: 'session',
API_KEY: 'api_key',
INTERNAL_JWT: 'internal_jwt',
} as const

export type AuthTypeValue = (typeof AuthType)[keyof typeof AuthType]

export interface AuthResult {
success: boolean
userId?: string
userName?: string | null
userEmail?: string | null
authType?: 'session' | 'api_key' | 'internal_jwt'
authType?: AuthTypeValue
apiKeyType?: 'personal' | 'workspace'
error?: string
}
Expand Down Expand Up @@ -46,14 +54,14 @@ async function resolveUserFromJwt(
}

if (userId) {
return { success: true, userId, authType: 'internal_jwt' }
return { success: true, userId, authType: AuthType.INTERNAL_JWT }
}

if (options.requireWorkflowId !== false) {
return { success: false, error: 'userId required for internal JWT calls' }
}

return { success: true, authType: 'internal_jwt' }
return { success: true, authType: AuthType.INTERNAL_JWT }
}

/**
Expand Down Expand Up @@ -146,7 +154,7 @@ export async function checkSessionOrInternalAuth(
userId: session.user.id,
userName: session.user.name,
userEmail: session.user.email,
authType: 'session',
authType: AuthType.SESSION,
}
}

Expand Down Expand Up @@ -195,7 +203,7 @@ export async function checkHybridAuth(
userId: session.user.id,
userName: session.user.name,
userEmail: session.user.email,
authType: 'session',
authType: AuthType.SESSION,
}
}

Expand All @@ -208,7 +216,7 @@ export async function checkHybridAuth(
return {
success: true,
userId: result.userId!,
authType: 'api_key',
authType: AuthType.API_KEY,
apiKeyType: result.keyType,
}
}
Expand Down
Loading
Loading