mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-08 13:45:03 -05:00
Compare commits
10 Commits
fix/visibi
...
fix/logs-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ebe751e8f | ||
|
|
f615be61f2 | ||
|
|
c9691fc437 | ||
|
|
7ce442f499 | ||
|
|
cf1792e408 | ||
|
|
36e6133a08 | ||
|
|
94ad777e5e | ||
|
|
6ef3b96395 | ||
|
|
8b6796eabe | ||
|
|
895eec3c41 |
@@ -5,7 +5,7 @@ import { eq } from 'drizzle-orm'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { generateAgentCard, generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
|
import { generateAgentCard, generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
|
||||||
import type { AgentCapabilities, AgentSkill } from '@/lib/a2a/types'
|
import type { AgentCapabilities, AgentSkill } from '@/lib/a2a/types'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { getRedisClient } from '@/lib/core/config/redis'
|
import { getRedisClient } from '@/lib/core/config/redis'
|
||||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
||||||
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
||||||
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!agent.agent.isPublished) {
|
if (!agent.agent.isPublished) {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success) {
|
if (!auth.success) {
|
||||||
return NextResponse.json({ error: 'Agent not published' }, { status: 404 })
|
return NextResponse.json({ error: 'Agent not published' }, { status: 404 })
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<Ro
|
|||||||
const { agentId } = await params
|
const { agentId } = await params
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise
|
|||||||
const { agentId } = await params
|
const { agentId } = await params
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
|
|||||||
const { agentId } = await params
|
const { agentId } = await params
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
logger.warn('A2A agent publish auth failed:', { error: auth.error, hasUserId: !!auth.userId })
|
logger.warn('A2A agent publish auth failed:', { error: auth.error, hasUserId: !!auth.userId })
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { v4 as uuidv4 } from 'uuid'
|
|||||||
import { generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
|
import { generateSkillsFromWorkflow } from '@/lib/a2a/agent-card'
|
||||||
import { A2A_DEFAULT_CAPABILITIES } from '@/lib/a2a/constants'
|
import { A2A_DEFAULT_CAPABILITIES } from '@/lib/a2a/constants'
|
||||||
import { sanitizeAgentName } from '@/lib/a2a/utils'
|
import { sanitizeAgentName } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
||||||
import { hasValidStartBlockInState } from '@/lib/workflows/triggers/trigger-utils'
|
import { hasValidStartBlockInState } from '@/lib/workflows/triggers/trigger-utils'
|
||||||
import { getWorkspaceById } from '@/lib/workspaces/permissions/utils'
|
import { getWorkspaceById } from '@/lib/workspaces/permissions/utils'
|
||||||
@@ -27,7 +27,7 @@ export const dynamic = 'force-dynamic'
|
|||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ export async function GET(request: NextRequest) {
|
|||||||
*/
|
*/
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { and, eq } from 'drizzle-orm'
|
|||||||
import { jwtDecode } from 'jwt-decode'
|
import { jwtDecode } from 'jwt-decode'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { evaluateScopeCoverage, type OAuthProvider, parseProvider } from '@/lib/oauth'
|
import { evaluateScopeCoverage, type OAuthProvider, parseProvider } from '@/lib/oauth'
|
||||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||||
@@ -81,7 +81,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const { provider: providerParam, workflowId, credentialId } = parseResult.data
|
const { provider: providerParam, workflowId, credentialId } = parseResult.data
|
||||||
|
|
||||||
// Authenticate requester (supports session, API key, internal JWT)
|
// Authenticate requester (supports session, API key, internal JWT)
|
||||||
const authResult = await checkHybridAuth(request)
|
const authResult = await checkSessionOrInternalAuth(request)
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthenticated credentials request rejected`)
|
logger.warn(`[${requestId}] Unauthenticated credentials request rejected`)
|
||||||
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
|
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
const mockRefreshTokenIfNeeded = vi.fn()
|
const mockRefreshTokenIfNeeded = vi.fn()
|
||||||
const mockGetOAuthToken = vi.fn()
|
const mockGetOAuthToken = vi.fn()
|
||||||
const mockAuthorizeCredentialUse = vi.fn()
|
const mockAuthorizeCredentialUse = vi.fn()
|
||||||
const mockCheckHybridAuth = vi.fn()
|
const mockCheckSessionOrInternalAuth = vi.fn()
|
||||||
|
|
||||||
const mockLogger = createMockLogger()
|
const mockLogger = createMockLogger()
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: mockCheckHybridAuth,
|
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
|
|
||||||
describe('credentialAccountUserId + providerId path', () => {
|
describe('credentialAccountUserId + providerId path', () => {
|
||||||
it('should reject unauthenticated requests', async () => {
|
it('should reject unauthenticated requests', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Authentication required',
|
error: 'Authentication required',
|
||||||
})
|
})
|
||||||
@@ -255,30 +255,8 @@ describe('OAuth Token API Routes', () => {
|
|||||||
expect(mockGetOAuthToken).not.toHaveBeenCalled()
|
expect(mockGetOAuthToken).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject API key authentication', async () => {
|
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
|
||||||
success: true,
|
|
||||||
authType: 'api_key',
|
|
||||||
userId: 'test-user-id',
|
|
||||||
})
|
|
||||||
|
|
||||||
const req = createMockRequest('POST', {
|
|
||||||
credentialAccountUserId: 'test-user-id',
|
|
||||||
providerId: 'google',
|
|
||||||
})
|
|
||||||
|
|
||||||
const { POST } = await import('@/app/api/auth/oauth/token/route')
|
|
||||||
|
|
||||||
const response = await POST(req)
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
expect(response.status).toBe(401)
|
|
||||||
expect(data).toHaveProperty('error', 'User not authenticated')
|
|
||||||
expect(mockGetOAuthToken).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject internal JWT authentication', async () => {
|
it('should reject internal JWT authentication', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'internal_jwt',
|
authType: 'internal_jwt',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
@@ -300,7 +278,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should reject requests for other users credentials', async () => {
|
it('should reject requests for other users credentials', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'attacker-user-id',
|
userId: 'attacker-user-id',
|
||||||
@@ -322,7 +300,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should allow session-authenticated users to access their own credentials', async () => {
|
it('should allow session-authenticated users to access their own credentials', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
@@ -345,7 +323,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return 404 when credential not found for user', async () => {
|
it('should return 404 when credential not found for user', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
@@ -373,7 +351,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
*/
|
*/
|
||||||
describe('GET handler', () => {
|
describe('GET handler', () => {
|
||||||
it('should return access token successfully', async () => {
|
it('should return access token successfully', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
@@ -402,7 +380,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
expect(response.status).toBe(200)
|
expect(response.status).toBe(200)
|
||||||
expect(data).toHaveProperty('accessToken', 'fresh-token')
|
expect(data).toHaveProperty('accessToken', 'fresh-token')
|
||||||
|
|
||||||
expect(mockCheckHybridAuth).toHaveBeenCalled()
|
expect(mockCheckSessionOrInternalAuth).toHaveBeenCalled()
|
||||||
expect(mockGetCredential).toHaveBeenCalledWith(mockRequestId, 'credential-id', 'test-user-id')
|
expect(mockGetCredential).toHaveBeenCalledWith(mockRequestId, 'credential-id', 'test-user-id')
|
||||||
expect(mockRefreshTokenIfNeeded).toHaveBeenCalled()
|
expect(mockRefreshTokenIfNeeded).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -421,7 +399,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle authentication failure', async () => {
|
it('should handle authentication failure', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Authentication required',
|
error: 'Authentication required',
|
||||||
})
|
})
|
||||||
@@ -440,7 +418,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle credential not found', async () => {
|
it('should handle credential not found', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
@@ -461,7 +439,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle missing access token', async () => {
|
it('should handle missing access token', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
@@ -487,7 +465,7 @@ describe('OAuth Token API Routes', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should handle token refresh failure', async () => {
|
it('should handle token refresh failure', async () => {
|
||||||
mockCheckHybridAuth.mockResolvedValueOnce({
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
||||||
success: true,
|
success: true,
|
||||||
authType: 'session',
|
authType: 'session',
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { getCredential, getOAuthToken, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
import { getCredential, getOAuthToken, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export async function POST(request: NextRequest) {
|
|||||||
providerId,
|
providerId,
|
||||||
})
|
})
|
||||||
|
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
|
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthorized request for credentialAccountUserId path`, {
|
logger.warn(`[${requestId}] Unauthorized request for credentialAccountUserId path`, {
|
||||||
success: auth.success,
|
success: auth.success,
|
||||||
@@ -187,7 +187,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const { credentialId } = parseResult.data
|
const { credentialId } = parseResult.data
|
||||||
|
|
||||||
// For GET requests, we only support session-based authentication
|
// For GET requests, we only support session-based authentication
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
|
if (!auth.success || auth.authType !== 'session' || !auth.userId) {
|
||||||
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
|
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function setupFileApiMocks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: authenticated,
|
success: authenticated,
|
||||||
userId: authenticated ? 'test-user-id' : undefined,
|
userId: authenticated ? 'test-user-id' : undefined,
|
||||||
error: authenticated ? undefined : 'Unauthorized',
|
error: authenticated ? undefined : 'Unauthorized',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import type { NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import type { StorageContext } from '@/lib/uploads/config'
|
import type { StorageContext } from '@/lib/uploads/config'
|
||||||
import { deleteFile, hasCloudStorage } from '@/lib/uploads/core/storage-service'
|
import { deleteFile, hasCloudStorage } from '@/lib/uploads/core/storage-service'
|
||||||
import { extractStorageKey, inferContextFromKey } from '@/lib/uploads/utils/file-utils'
|
import { extractStorageKey, inferContextFromKey } from '@/lib/uploads/utils/file-utils'
|
||||||
@@ -24,7 +24,7 @@ const logger = createLogger('FilesDeleteAPI')
|
|||||||
*/
|
*/
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn('Unauthorized file delete request', {
|
logger.warn('Unauthorized file delete request', {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import type { StorageContext } from '@/lib/uploads/config'
|
import type { StorageContext } from '@/lib/uploads/config'
|
||||||
import { hasCloudStorage } from '@/lib/uploads/core/storage-service'
|
import { hasCloudStorage } from '@/lib/uploads/core/storage-service'
|
||||||
import { verifyFileAccess } from '@/app/api/files/authorization'
|
import { verifyFileAccess } from '@/app/api/files/authorization'
|
||||||
@@ -12,7 +12,7 @@ export const dynamic = 'force-dynamic'
|
|||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn('Unauthorized download URL request', {
|
logger.warn('Unauthorized download URL request', {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function setupFileApiMocks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: authenticated,
|
success: authenticated,
|
||||||
userId: authenticated ? 'test-user-id' : undefined,
|
userId: authenticated ? 'test-user-id' : undefined,
|
||||||
error: authenticated ? undefined : 'Unauthorized',
|
error: authenticated ? undefined : 'Unauthorized',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import path from 'path'
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import binaryExtensionsList from 'binary-extensions'
|
import binaryExtensionsList from 'binary-extensions'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import {
|
import {
|
||||||
secureFetchWithPinnedIP,
|
secureFetchWithPinnedIP,
|
||||||
validateUrlWithDNS,
|
validateUrlWithDNS,
|
||||||
@@ -66,7 +66,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: true })
|
const authResult = await checkInternalAuth(request, { requireWorkflowId: true })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn('Unauthorized file parse request', {
|
logger.warn('Unauthorized file parse request', {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ describe('File Serve API Route', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: true,
|
success: true,
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
}),
|
}),
|
||||||
@@ -165,7 +165,7 @@ describe('File Serve API Route', () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: true,
|
success: true,
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
}),
|
}),
|
||||||
@@ -226,7 +226,7 @@ describe('File Serve API Route', () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: true,
|
success: true,
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
}),
|
}),
|
||||||
@@ -291,7 +291,7 @@ describe('File Serve API Route', () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: true,
|
success: true,
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
}),
|
}),
|
||||||
@@ -350,7 +350,7 @@ describe('File Serve API Route', () => {
|
|||||||
for (const test of contentTypeTests) {
|
for (const test of contentTypeTests) {
|
||||||
it(`should serve ${test.ext} file with correct content type`, async () => {
|
it(`should serve ${test.ext} file with correct content type`, async () => {
|
||||||
vi.doMock('@/lib/auth/hybrid', () => ({
|
vi.doMock('@/lib/auth/hybrid', () => ({
|
||||||
checkHybridAuth: vi.fn().mockResolvedValue({
|
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({
|
||||||
success: true,
|
success: true,
|
||||||
userId: 'test-user-id',
|
userId: 'test-user-id',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { readFile } from 'fs/promises'
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import type { NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads'
|
import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads'
|
||||||
import type { StorageContext } from '@/lib/uploads/config'
|
import type { StorageContext } from '@/lib/uploads/config'
|
||||||
import { downloadFile } from '@/lib/uploads/core/storage-service'
|
import { downloadFile } from '@/lib/uploads/core/storage-service'
|
||||||
@@ -49,7 +49,7 @@ export async function GET(
|
|||||||
return await handleLocalFilePublic(fullPath)
|
return await handleLocalFilePublic(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn('Unauthorized file access attempt', {
|
logger.warn('Unauthorized file access attempt', {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants'
|
import { SUPPORTED_FIELD_TYPES } from '@/lib/knowledge/constants'
|
||||||
import { createTagDefinition, getTagDefinitions } from '@/lib/knowledge/tags/service'
|
import { createTagDefinition, getTagDefinitions } from '@/lib/knowledge/tags/service'
|
||||||
import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils'
|
import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils'
|
||||||
@@ -19,19 +19,11 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
|
|||||||
try {
|
try {
|
||||||
logger.info(`[${requestId}] Getting tag definitions for knowledge base ${knowledgeBaseId}`)
|
logger.info(`[${requestId}] Getting tag definitions for knowledge base ${knowledgeBaseId}`)
|
||||||
|
|
||||||
const auth = await checkHybridAuth(req, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false })
|
||||||
if (!auth.success) {
|
if (!auth.success) {
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow session and internal JWT auth (not API key)
|
|
||||||
if (auth.authType === 'api_key') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'API key auth not supported for this endpoint' },
|
|
||||||
{ status: 401 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For session auth, verify KB access. Internal JWT is trusted.
|
// For session auth, verify KB access. Internal JWT is trusted.
|
||||||
if (auth.authType === 'session' && auth.userId) {
|
if (auth.authType === 'session' && auth.userId) {
|
||||||
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, auth.userId)
|
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, auth.userId)
|
||||||
@@ -64,19 +56,11 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
|||||||
try {
|
try {
|
||||||
logger.info(`[${requestId}] Creating tag definition for knowledge base ${knowledgeBaseId}`)
|
logger.info(`[${requestId}] Creating tag definition for knowledge base ${knowledgeBaseId}`)
|
||||||
|
|
||||||
const auth = await checkHybridAuth(req, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false })
|
||||||
if (!auth.success) {
|
if (!auth.success) {
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only allow session and internal JWT auth (not API key)
|
|
||||||
if (auth.authType === 'api_key') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'API key auth not supported for this endpoint' },
|
|
||||||
{ status: 401 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For session auth, verify KB access. Internal JWT is trusted.
|
// For session auth, verify KB access. Internal JWT is trusted.
|
||||||
if (auth.authType === 'session' && auth.userId) {
|
if (auth.authType === 'session' && auth.userId) {
|
||||||
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, auth.userId)
|
const accessCheck = await checkKnowledgeBaseAccess(knowledgeBaseId, auth.userId)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { and, eq, inArray } from 'drizzle-orm'
|
import { and, eq, inArray } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
|
import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export async function GET(
|
|||||||
try {
|
try {
|
||||||
const { executionId } = await params
|
const { executionId } = await params
|
||||||
|
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthorized execution data access attempt for: ${executionId}`)
|
logger.warn(`[${requestId}] Unauthorized execution data access attempt for: ${executionId}`)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { and, eq } from 'drizzle-orm'
|
import { and, eq } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ async function validateMemoryAccess(
|
|||||||
requestId: string,
|
requestId: string,
|
||||||
action: 'read' | 'write'
|
action: 'read' | 'write'
|
||||||
): Promise<{ userId: string } | { error: NextResponse }> {
|
): Promise<{ userId: string } | { error: NextResponse }> {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthorized memory ${action} attempt`)
|
logger.warn(`[${requestId}] Unauthorized memory ${action} attempt`)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { memory } from '@sim/db/schema'
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { and, eq, isNull, like } from 'drizzle-orm'
|
import { and, eq, isNull, like } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request)
|
const authResult = await checkInternalAuth(request)
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthorized memory access attempt`)
|
logger.warn(`[${requestId}] Unauthorized memory access attempt`)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -89,7 +89,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request)
|
const authResult = await checkInternalAuth(request)
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthorized memory creation attempt`)
|
logger.warn(`[${requestId}] Unauthorized memory creation attempt`)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -228,7 +228,7 @@ export async function DELETE(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request)
|
const authResult = await checkInternalAuth(request)
|
||||||
if (!authResult.success || !authResult.userId) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn(`[${requestId}] Unauthorized memory deletion attempt`)
|
logger.warn(`[${requestId}] Unauthorized memory deletion attempt`)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient } from '@/lib/a2a/utils'
|
import { createA2AClient } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
const logger = createLogger('A2ACancelTaskAPI')
|
const logger = createLogger('A2ACancelTaskAPI')
|
||||||
@@ -20,7 +20,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(`[${requestId}] Unauthorized A2A cancel task attempt`)
|
logger.warn(`[${requestId}] Unauthorized A2A cancel task attempt`)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient } from '@/lib/a2a/utils'
|
import { createA2AClient } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -20,7 +20,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient } from '@/lib/a2a/utils'
|
import { createA2AClient } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(`[${requestId}] Unauthorized A2A get agent card attempt: ${authResult.error}`)
|
logger.warn(`[${requestId}] Unauthorized A2A get agent card attempt: ${authResult.error}`)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient } from '@/lib/a2a/utils'
|
import { createA2AClient } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient } from '@/lib/a2a/utils'
|
import { createA2AClient } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(`[${requestId}] Unauthorized A2A get task attempt: ${authResult.error}`)
|
logger.warn(`[${requestId}] Unauthorized A2A get task attempt: ${authResult.error}`)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
|
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
const logger = createLogger('A2AResubscribeAPI')
|
const logger = createLogger('A2AResubscribeAPI')
|
||||||
@@ -27,7 +27,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(`[${requestId}] Unauthorized A2A resubscribe attempt`)
|
logger.warn(`[${requestId}] Unauthorized A2A resubscribe attempt`)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
|
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(`[${requestId}] Unauthorized A2A send message attempt: ${authResult.error}`)
|
logger.warn(`[${requestId}] Unauthorized A2A send message attempt: ${authResult.error}`)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { createA2AClient } from '@/lib/a2a/utils'
|
import { createA2AClient } from '@/lib/a2a/utils'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
|
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
logger.warn(`[${requestId}] Unauthorized A2A set push notification attempt`, {
|
logger.warn(`[${requestId}] Unauthorized A2A set push notification attempt`, {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { getUserUsageLogs, type UsageLogSource } from '@/lib/billing/core/usage-log'
|
import { getUserUsageLogs, type UsageLogSource } from '@/lib/billing/core/usage-log'
|
||||||
|
|
||||||
const logger = createLogger('UsageLogsAPI')
|
const logger = createLogger('UsageLogsAPI')
|
||||||
@@ -20,7 +20,7 @@ const QuerySchema = z.object({
|
|||||||
*/
|
*/
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(req, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(req, { requireWorkflowId: false })
|
||||||
|
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ function FileCard({ file, isExecutionFile = false, workspaceId }: FileCardProps)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isExecutionFile) {
|
if (isExecutionFile) {
|
||||||
const serveUrl =
|
const serveUrl = `/api/files/serve/${encodeURIComponent(file.key)}?context=execution`
|
||||||
file.url || `/api/files/serve/${encodeURIComponent(file.key)}?context=execution`
|
|
||||||
window.open(serveUrl, '_blank')
|
window.open(serveUrl, '_blank')
|
||||||
logger.info(`Opened execution file serve URL: ${serveUrl}`)
|
logger.info(`Opened execution file serve URL: ${serveUrl}`)
|
||||||
} else {
|
} else {
|
||||||
@@ -88,16 +87,12 @@ function FileCard({ file, isExecutionFile = false, workspaceId }: FileCardProps)
|
|||||||
logger.warn(
|
logger.warn(
|
||||||
`Could not construct viewer URL for file: ${file.name}, falling back to serve URL`
|
`Could not construct viewer URL for file: ${file.name}, falling back to serve URL`
|
||||||
)
|
)
|
||||||
const serveUrl =
|
const serveUrl = `/api/files/serve/${encodeURIComponent(file.key)}?context=workspace`
|
||||||
file.url || `/api/files/serve/${encodeURIComponent(file.key)}?context=workspace`
|
|
||||||
window.open(serveUrl, '_blank')
|
window.open(serveUrl, '_blank')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to download file ${file.name}:`, error)
|
logger.error(`Failed to download file ${file.name}:`, error)
|
||||||
if (file.url) {
|
|
||||||
window.open(file.url, '_blank')
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDownloading(false)
|
setIsDownloading(false)
|
||||||
}
|
}
|
||||||
@@ -198,8 +193,7 @@ export function FileDownload({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isExecutionFile) {
|
if (isExecutionFile) {
|
||||||
const serveUrl =
|
const serveUrl = `/api/files/serve/${encodeURIComponent(file.key)}?context=execution`
|
||||||
file.url || `/api/files/serve/${encodeURIComponent(file.key)}?context=execution`
|
|
||||||
window.open(serveUrl, '_blank')
|
window.open(serveUrl, '_blank')
|
||||||
logger.info(`Opened execution file serve URL: ${serveUrl}`)
|
logger.info(`Opened execution file serve URL: ${serveUrl}`)
|
||||||
} else {
|
} else {
|
||||||
@@ -212,16 +206,12 @@ export function FileDownload({
|
|||||||
logger.warn(
|
logger.warn(
|
||||||
`Could not construct viewer URL for file: ${file.name}, falling back to serve URL`
|
`Could not construct viewer URL for file: ${file.name}, falling back to serve URL`
|
||||||
)
|
)
|
||||||
const serveUrl =
|
const serveUrl = `/api/files/serve/${encodeURIComponent(file.key)}?context=workspace`
|
||||||
file.url || `/api/files/serve/${encodeURIComponent(file.key)}?context=workspace`
|
|
||||||
window.open(serveUrl, '_blank')
|
window.open(serveUrl, '_blank')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to download file ${file.name}:`, error)
|
logger.error(`Failed to download file ${file.name}:`, error)
|
||||||
if (file.url) {
|
|
||||||
window.open(file.url, '_blank')
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDownloading(false)
|
setIsDownloading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { AgentIcon } from '@/components/icons'
|
import { AgentIcon } from '@/components/icons'
|
||||||
import { isHosted } from '@/lib/core/config/feature-flags'
|
|
||||||
import type { BlockConfig } from '@/blocks/types'
|
import type { BlockConfig } from '@/blocks/types'
|
||||||
import { AuthMode } from '@/blocks/types'
|
import { AuthMode } from '@/blocks/types'
|
||||||
|
import { getApiKeyCondition } from '@/blocks/utils'
|
||||||
import {
|
import {
|
||||||
getBaseModelProviders,
|
getBaseModelProviders,
|
||||||
getHostedModels,
|
|
||||||
getMaxTemperature,
|
getMaxTemperature,
|
||||||
getProviderIcon,
|
getProviderIcon,
|
||||||
getReasoningEffortValuesForModel,
|
getReasoningEffortValuesForModel,
|
||||||
@@ -17,15 +16,6 @@ import {
|
|||||||
providers,
|
providers,
|
||||||
supportsTemperature,
|
supportsTemperature,
|
||||||
} from '@/providers/utils'
|
} from '@/providers/utils'
|
||||||
|
|
||||||
const getCurrentOllamaModels = () => {
|
|
||||||
return useProvidersStore.getState().providers.ollama.models
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCurrentVLLMModels = () => {
|
|
||||||
return useProvidersStore.getState().providers.vllm.models
|
|
||||||
}
|
|
||||||
|
|
||||||
import { useProvidersStore } from '@/stores/providers'
|
import { useProvidersStore } from '@/stores/providers'
|
||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
@@ -421,23 +411,7 @@ Return ONLY the JSON array.`,
|
|||||||
password: true,
|
password: true,
|
||||||
connectionDroppable: false,
|
connectionDroppable: false,
|
||||||
required: true,
|
required: true,
|
||||||
// Hide API key for hosted models, Ollama models, vLLM models, Vertex models (uses OAuth), and Bedrock (uses AWS credentials)
|
condition: getApiKeyCondition(),
|
||||||
condition: isHosted
|
|
||||||
? {
|
|
||||||
field: 'model',
|
|
||||||
value: [...getHostedModels(), ...providers.vertex.models, ...providers.bedrock.models],
|
|
||||||
not: true, // Show for all models EXCEPT those listed
|
|
||||||
}
|
|
||||||
: () => ({
|
|
||||||
field: 'model',
|
|
||||||
value: [
|
|
||||||
...getCurrentOllamaModels(),
|
|
||||||
...getCurrentVLLMModels(),
|
|
||||||
...providers.vertex.models,
|
|
||||||
...providers.bedrock.models,
|
|
||||||
],
|
|
||||||
not: true, // Show for all models EXCEPT Ollama, vLLM, Vertex, and Bedrock models
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'memoryType',
|
id: 'memoryType',
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export interface SubBlockConfig {
|
|||||||
not?: boolean
|
not?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
| (() => {
|
| ((values?: Record<string, unknown>) => {
|
||||||
field: string
|
field: string
|
||||||
value: string | number | boolean | Array<string | number | boolean>
|
value: string | number | boolean | Array<string | number | boolean>
|
||||||
not?: boolean
|
not?: boolean
|
||||||
@@ -261,7 +261,7 @@ export interface SubBlockConfig {
|
|||||||
not?: boolean
|
not?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
| (() => {
|
| ((values?: Record<string, unknown>) => {
|
||||||
field: string
|
field: string
|
||||||
value: string | number | boolean | Array<string | number | boolean>
|
value: string | number | boolean | Array<string | number | boolean>
|
||||||
not?: boolean
|
not?: boolean
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isHosted } from '@/lib/core/config/feature-flags'
|
import { isHosted } from '@/lib/core/config/feature-flags'
|
||||||
import type { BlockOutput, OutputFieldDefinition, SubBlockConfig } from '@/blocks/types'
|
import type { BlockOutput, OutputFieldDefinition, SubBlockConfig } from '@/blocks/types'
|
||||||
import { getHostedModels, providers } from '@/providers/utils'
|
import { getHostedModels, getProviderFromModel, providers } from '@/providers/utils'
|
||||||
import { useProvidersStore } from '@/stores/providers/store'
|
import { useProvidersStore } from '@/stores/providers/store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,11 +48,54 @@ const getCurrentOllamaModels = () => {
|
|||||||
return useProvidersStore.getState().providers.ollama.models
|
return useProvidersStore.getState().providers.ollama.models
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function buildModelVisibilityCondition(model: string, shouldShow: boolean) {
|
||||||
* Helper to get current vLLM models from store
|
if (!model) {
|
||||||
*/
|
return { field: 'model', value: '__no_model_selected__' }
|
||||||
const getCurrentVLLMModels = () => {
|
}
|
||||||
return useProvidersStore.getState().providers.vllm.models
|
|
||||||
|
return shouldShow ? { field: 'model', value: model } : { field: 'model', value: model, not: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldRequireApiKeyForModel(model: string): boolean {
|
||||||
|
const normalizedModel = model.trim().toLowerCase()
|
||||||
|
if (!normalizedModel) return false
|
||||||
|
|
||||||
|
const hostedModels = getHostedModels()
|
||||||
|
const isHostedModel = hostedModels.some(
|
||||||
|
(hostedModel) => hostedModel.toLowerCase() === normalizedModel
|
||||||
|
)
|
||||||
|
if (isHosted && isHostedModel) return false
|
||||||
|
|
||||||
|
if (normalizedModel.startsWith('vertex/') || normalizedModel.startsWith('bedrock/')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedModel.startsWith('vllm/')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentOllamaModels = getCurrentOllamaModels()
|
||||||
|
if (currentOllamaModels.some((ollamaModel) => ollamaModel.toLowerCase() === normalizedModel)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHosted) {
|
||||||
|
try {
|
||||||
|
const providerId = getProviderFromModel(model)
|
||||||
|
if (
|
||||||
|
providerId === 'ollama' ||
|
||||||
|
providerId === 'vllm' ||
|
||||||
|
providerId === 'vertex' ||
|
||||||
|
providerId === 'bedrock'
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If model resolution fails, fall through and require an API key.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,22 +103,11 @@ const getCurrentVLLMModels = () => {
|
|||||||
* Handles hosted vs self-hosted environments and excludes providers that don't need API key.
|
* Handles hosted vs self-hosted environments and excludes providers that don't need API key.
|
||||||
*/
|
*/
|
||||||
export function getApiKeyCondition() {
|
export function getApiKeyCondition() {
|
||||||
return isHosted
|
return (values?: Record<string, unknown>) => {
|
||||||
? {
|
const model = typeof values?.model === 'string' ? values.model : ''
|
||||||
field: 'model',
|
const shouldShow = shouldRequireApiKeyForModel(model)
|
||||||
value: [...getHostedModels(), ...providers.vertex.models, ...providers.bedrock.models],
|
return buildModelVisibilityCondition(model, shouldShow)
|
||||||
not: true,
|
|
||||||
}
|
}
|
||||||
: () => ({
|
|
||||||
field: 'model',
|
|
||||||
value: [
|
|
||||||
...getCurrentOllamaModels(),
|
|
||||||
...getCurrentVLLMModels(),
|
|
||||||
...providers.vertex.models,
|
|
||||||
...providers.bedrock.models,
|
|
||||||
],
|
|
||||||
not: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -378,6 +378,9 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
if (ctx.workflowId) {
|
if (ctx.workflowId) {
|
||||||
params.workflowId = ctx.workflowId
|
params.workflowId = ctx.workflowId
|
||||||
}
|
}
|
||||||
|
if (ctx.userId) {
|
||||||
|
params.userId = ctx.userId
|
||||||
|
}
|
||||||
|
|
||||||
const url = buildAPIUrl('/api/tools/custom', params)
|
const url = buildAPIUrl('/api/tools/custom', params)
|
||||||
const response = await fetch(url.toString(), {
|
const response = await fetch(url.toString(), {
|
||||||
@@ -488,7 +491,9 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
usageControl: tool.usageControl || 'auto',
|
usageControl: tool.usageControl || 'auto',
|
||||||
executeFunction: async (callParams: Record<string, any>) => {
|
executeFunction: async (callParams: Record<string, any>) => {
|
||||||
const headers = await buildAuthHeaders()
|
const headers = await buildAuthHeaders()
|
||||||
const execUrl = buildAPIUrl('/api/mcp/tools/execute')
|
const execParams: Record<string, string> = {}
|
||||||
|
if (ctx.userId) execParams.userId = ctx.userId
|
||||||
|
const execUrl = buildAPIUrl('/api/mcp/tools/execute', execParams)
|
||||||
|
|
||||||
const execResponse = await fetch(execUrl.toString(), {
|
const execResponse = await fetch(execUrl.toString(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -597,6 +602,7 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
serverId,
|
serverId,
|
||||||
workspaceId: ctx.workspaceId,
|
workspaceId: ctx.workspaceId,
|
||||||
workflowId: ctx.workflowId,
|
workflowId: ctx.workflowId,
|
||||||
|
...(ctx.userId ? { userId: ctx.userId } : {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const maxAttempts = 2
|
const maxAttempts = 2
|
||||||
@@ -671,7 +677,9 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
usageControl: tool.usageControl || 'auto',
|
usageControl: tool.usageControl || 'auto',
|
||||||
executeFunction: async (callParams: Record<string, any>) => {
|
executeFunction: async (callParams: Record<string, any>) => {
|
||||||
const headers = await buildAuthHeaders()
|
const headers = await buildAuthHeaders()
|
||||||
const execUrl = buildAPIUrl('/api/mcp/tools/execute')
|
const discoverExecParams: Record<string, string> = {}
|
||||||
|
if (ctx.userId) discoverExecParams.userId = ctx.userId
|
||||||
|
const execUrl = buildAPIUrl('/api/mcp/tools/execute', discoverExecParams)
|
||||||
|
|
||||||
const execResponse = await fetch(execUrl.toString(), {
|
const execResponse = await fetch(execUrl.toString(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1056,6 +1064,7 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
responseFormat: providerRequest.responseFormat,
|
responseFormat: providerRequest.responseFormat,
|
||||||
workflowId: providerRequest.workflowId,
|
workflowId: providerRequest.workflowId,
|
||||||
workspaceId: ctx.workspaceId,
|
workspaceId: ctx.workspaceId,
|
||||||
|
userId: ctx.userId,
|
||||||
stream: providerRequest.stream,
|
stream: providerRequest.stream,
|
||||||
messages: 'messages' in providerRequest ? providerRequest.messages : undefined,
|
messages: 'messages' in providerRequest ? providerRequest.messages : undefined,
|
||||||
environmentVariables: ctx.environmentVariables || {},
|
environmentVariables: ctx.environmentVariables || {},
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export class EvaluatorBlockHandler implements BlockHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = buildAPIUrl('/api/providers')
|
const url = buildAPIUrl('/api/providers', ctx.userId ? { userId: ctx.userId } : {})
|
||||||
|
|
||||||
const providerRequest: Record<string, any> = {
|
const providerRequest: Record<string, any> = {
|
||||||
provider: providerId,
|
provider: providerId,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export class RouterBlockHandler implements BlockHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const url = new URL('/api/providers', getBaseUrl())
|
const url = new URL('/api/providers', getBaseUrl())
|
||||||
|
if (ctx.userId) url.searchParams.set('userId', ctx.userId)
|
||||||
|
|
||||||
const messages = [{ role: 'user', content: routerConfig.prompt }]
|
const messages = [{ role: 'user', content: routerConfig.prompt }]
|
||||||
const systemPrompt = generateRouterPrompt(routerConfig.prompt, targetBlocks)
|
const systemPrompt = generateRouterPrompt(routerConfig.prompt, targetBlocks)
|
||||||
@@ -209,6 +210,7 @@ export class RouterBlockHandler implements BlockHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const url = new URL('/api/providers', getBaseUrl())
|
const url = new URL('/api/providers', getBaseUrl())
|
||||||
|
if (ctx.userId) url.searchParams.set('userId', ctx.userId)
|
||||||
|
|
||||||
const messages = [{ role: 'user', content: routerConfig.context }]
|
const messages = [{ role: 'user', content: routerConfig.context }]
|
||||||
const systemPrompt = generateRouterV2Prompt(routerConfig.context, routes)
|
const systemPrompt = generateRouterV2Prompt(routerConfig.context, routes)
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { db } from '@sim/db'
|
|||||||
import { account, workflow as workflowTable } from '@sim/db/schema'
|
import { account, workflow as workflowTable } from '@sim/db/schema'
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm'
|
||||||
import type { NextRequest } from 'next/server'
|
import type { NextRequest } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||||
|
|
||||||
export interface CredentialAccessResult {
|
export interface CredentialAccessResult {
|
||||||
ok: boolean
|
ok: boolean
|
||||||
error?: string
|
error?: string
|
||||||
authType?: 'session' | 'api_key' | 'internal_jwt'
|
authType?: 'session' | 'internal_jwt'
|
||||||
requesterUserId?: string
|
requesterUserId?: string
|
||||||
credentialOwnerUserId?: string
|
credentialOwnerUserId?: string
|
||||||
workspaceId?: string
|
workspaceId?: string
|
||||||
@@ -16,10 +16,10 @@ export interface CredentialAccessResult {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Centralizes auth + collaboration rules for credential use.
|
* Centralizes auth + collaboration rules for credential use.
|
||||||
* - Uses checkHybridAuth to authenticate the caller
|
* - Uses checkSessionOrInternalAuth to authenticate the caller
|
||||||
* - Fetches credential owner
|
* - Fetches credential owner
|
||||||
* - Authorization rules:
|
* - Authorization rules:
|
||||||
* - session/api_key: allow if requester owns the credential; otherwise require workflowId and
|
* - session: allow if requester owns the credential; otherwise require workflowId and
|
||||||
* verify BOTH requester and owner have access to the workflow's workspace
|
* verify BOTH requester and owner have access to the workflow's workspace
|
||||||
* - internal_jwt: require workflowId (by default) and verify credential owner has access to the
|
* - internal_jwt: require workflowId (by default) and verify credential owner has access to the
|
||||||
* workflow's workspace (requester identity is the system/workflow)
|
* workflow's workspace (requester identity is the system/workflow)
|
||||||
@@ -30,7 +30,9 @@ export async function authorizeCredentialUse(
|
|||||||
): Promise<CredentialAccessResult> {
|
): Promise<CredentialAccessResult> {
|
||||||
const { credentialId, workflowId, requireWorkflowIdForInternal = true } = params
|
const { credentialId, workflowId, requireWorkflowIdForInternal = true } = params
|
||||||
|
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: requireWorkflowIdForInternal })
|
const auth = await checkSessionOrInternalAuth(request, {
|
||||||
|
requireWorkflowId: requireWorkflowIdForInternal,
|
||||||
|
})
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
return { ok: false, error: auth.error || 'Authentication required' }
|
return { ok: false, error: auth.error || 'Authentication required' }
|
||||||
}
|
}
|
||||||
@@ -52,7 +54,7 @@ export async function authorizeCredentialUse(
|
|||||||
if (auth.authType !== 'internal_jwt' && auth.userId === credentialOwnerUserId) {
|
if (auth.authType !== 'internal_jwt' && auth.userId === credentialOwnerUserId) {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
authType: auth.authType,
|
authType: auth.authType as CredentialAccessResult['authType'],
|
||||||
requesterUserId: auth.userId,
|
requesterUserId: auth.userId,
|
||||||
credentialOwnerUserId,
|
credentialOwnerUserId,
|
||||||
}
|
}
|
||||||
@@ -85,14 +87,14 @@ export async function authorizeCredentialUse(
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
authType: auth.authType,
|
authType: auth.authType as CredentialAccessResult['authType'],
|
||||||
requesterUserId: auth.userId,
|
requesterUserId: auth.userId,
|
||||||
credentialOwnerUserId,
|
credentialOwnerUserId,
|
||||||
workspaceId: wf.workspaceId,
|
workspaceId: wf.workspaceId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session/API key: verify BOTH requester and owner belong to the workflow's workspace
|
// Session: verify BOTH requester and owner belong to the workflow's workspace
|
||||||
const requesterPerm = await getUserEntityPermissions(auth.userId, 'workspace', wf.workspaceId)
|
const requesterPerm = await getUserEntityPermissions(auth.userId, 'workspace', wf.workspaceId)
|
||||||
const ownerPerm = await getUserEntityPermissions(
|
const ownerPerm = await getUserEntityPermissions(
|
||||||
credentialOwnerUserId,
|
credentialOwnerUserId,
|
||||||
@@ -105,7 +107,7 @@ export async function authorizeCredentialUse(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
authType: auth.authType,
|
authType: auth.authType as CredentialAccessResult['authType'],
|
||||||
requesterUserId: auth.userId,
|
requesterUserId: auth.userId,
|
||||||
credentialOwnerUserId,
|
credentialOwnerUserId,
|
||||||
workspaceId: wf.workspaceId,
|
workspaceId: wf.workspaceId,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import type { NextRequest, NextResponse } from 'next/server'
|
import type { NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { createMcpErrorResponse } from '@/lib/mcp/utils'
|
import { createMcpErrorResponse } from '@/lib/mcp/utils'
|
||||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||||
@@ -43,7 +43,7 @@ async function validateMcpAuth(
|
|||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auth = await checkHybridAuth(request, { requireWorkflowId: false })
|
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
|
||||||
if (!auth.success || !auth.userId) {
|
if (!auth.success || !auth.userId) {
|
||||||
logger.warn(`[${requestId}] Authentication failed: ${auth.error}`)
|
logger.warn(`[${requestId}] Authentication failed: ${auth.error}`)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
validateTypeformSignature,
|
validateTypeformSignature,
|
||||||
verifyProviderWebhook,
|
verifyProviderWebhook,
|
||||||
} from '@/lib/webhooks/utils.server'
|
} from '@/lib/webhooks/utils.server'
|
||||||
|
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
|
||||||
import { executeWebhookJob } from '@/background/webhook-execution'
|
import { executeWebhookJob } from '@/background/webhook-execution'
|
||||||
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
|
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
|
||||||
import { isGitHubEventMatch } from '@/triggers/github/utils'
|
import { isGitHubEventMatch } from '@/triggers/github/utils'
|
||||||
@@ -1003,10 +1004,23 @@ export async function queueWebhookExecution(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!foundWorkflow.workspaceId) {
|
||||||
|
logger.error(`[${options.requestId}] Workflow ${foundWorkflow.id} has no workspaceId`)
|
||||||
|
return NextResponse.json({ error: 'Workflow has no associated workspace' }, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const actorUserId = await getWorkspaceBilledAccountUserId(foundWorkflow.workspaceId)
|
||||||
|
if (!actorUserId) {
|
||||||
|
logger.error(
|
||||||
|
`[${options.requestId}] No billing account for workspace ${foundWorkflow.workspaceId}`
|
||||||
|
)
|
||||||
|
return NextResponse.json({ error: 'Unable to resolve billing account' }, { status: 500 })
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
webhookId: foundWebhook.id,
|
webhookId: foundWebhook.id,
|
||||||
workflowId: foundWorkflow.id,
|
workflowId: foundWorkflow.id,
|
||||||
userId: foundWorkflow.userId,
|
userId: actorUserId,
|
||||||
provider: foundWebhook.provider,
|
provider: foundWebhook.provider,
|
||||||
body,
|
body,
|
||||||
headers,
|
headers,
|
||||||
@@ -1017,7 +1031,7 @@ export async function queueWebhookExecution(
|
|||||||
|
|
||||||
const jobQueue = await getJobQueue()
|
const jobQueue = await getJobQueue()
|
||||||
const jobId = await jobQueue.enqueue('webhook-execution', payload, {
|
const jobId = await jobQueue.enqueue('webhook-execution', payload, {
|
||||||
metadata: { workflowId: foundWorkflow.id, userId: foundWorkflow.userId },
|
metadata: { workflowId: foundWorkflow.id, userId: actorUserId },
|
||||||
})
|
})
|
||||||
logger.info(
|
logger.info(
|
||||||
`[${options.requestId}] Queued webhook execution task ${jobId} for ${foundWebhook.provider} webhook`
|
`[${options.requestId}] Queued webhook execution task ${jobId} for ${foundWebhook.provider} webhook`
|
||||||
|
|||||||
@@ -156,6 +156,15 @@ describe('evaluateSubBlockCondition', () => {
|
|||||||
expect(evaluateSubBlockCondition(condition, values)).toBe(true)
|
expect(evaluateSubBlockCondition(condition, values)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.concurrent('passes current values into function conditions', () => {
|
||||||
|
const condition = (values?: Record<string, unknown>) => ({
|
||||||
|
field: 'model',
|
||||||
|
value: typeof values?.model === 'string' ? values.model : '__no_model_selected__',
|
||||||
|
})
|
||||||
|
const values = { model: 'ollama/gemma3:4b' }
|
||||||
|
expect(evaluateSubBlockCondition(condition, values)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it.concurrent('handles boolean values', () => {
|
it.concurrent('handles boolean values', () => {
|
||||||
const condition = { field: 'enabled', value: true }
|
const condition = { field: 'enabled', value: true }
|
||||||
const values = { enabled: true }
|
const values = { enabled: true }
|
||||||
|
|||||||
@@ -100,11 +100,14 @@ export function resolveCanonicalMode(
|
|||||||
* Evaluate a subblock condition against a map of raw values.
|
* Evaluate a subblock condition against a map of raw values.
|
||||||
*/
|
*/
|
||||||
export function evaluateSubBlockCondition(
|
export function evaluateSubBlockCondition(
|
||||||
condition: SubBlockCondition | (() => SubBlockCondition) | undefined,
|
condition:
|
||||||
|
| SubBlockCondition
|
||||||
|
| ((values?: Record<string, unknown>) => SubBlockCondition)
|
||||||
|
| undefined,
|
||||||
values: Record<string, unknown>
|
values: Record<string, unknown>
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!condition) return true
|
if (!condition) return true
|
||||||
const actual = typeof condition === 'function' ? condition() : condition
|
const actual = typeof condition === 'function' ? condition(values) : condition
|
||||||
const fieldValue = values[actual.field]
|
const fieldValue = values[actual.field]
|
||||||
const valueMatch = Array.isArray(actual.value)
|
const valueMatch = Array.isArray(actual.value)
|
||||||
? fieldValue != null &&
|
? fieldValue != null &&
|
||||||
|
|||||||
@@ -961,6 +961,7 @@ async function executeMcpTool(
|
|||||||
|
|
||||||
const workspaceId = params._context?.workspaceId || executionContext?.workspaceId
|
const workspaceId = params._context?.workspaceId || executionContext?.workspaceId
|
||||||
const workflowId = params._context?.workflowId || executionContext?.workflowId
|
const workflowId = params._context?.workflowId || executionContext?.workflowId
|
||||||
|
const userId = params._context?.userId || executionContext?.userId
|
||||||
|
|
||||||
if (!workspaceId) {
|
if (!workspaceId) {
|
||||||
return {
|
return {
|
||||||
@@ -1002,7 +1003,12 @@ async function executeMcpTool(
|
|||||||
hasToolSchema: !!toolSchema,
|
hasToolSchema: !!toolSchema,
|
||||||
})
|
})
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/api/mcp/tools/execute`, {
|
const mcpUrl = new URL('/api/mcp/tools/execute', baseUrl)
|
||||||
|
if (userId) {
|
||||||
|
mcpUrl.searchParams.set('userId', userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(mcpUrl.toString(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
|
|||||||
Reference in New Issue
Block a user