mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(webhook-modal): on copy do not change webhook url, fix auth to use regular perms system (#812)
* fix(webhook-modal): don't regenerate webhook url on copy * fix authentication checks for webhook saves
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getUserEntityPermissions } from '@/lib/permissions/utils'
|
||||
import { db } from '@/db'
|
||||
import { webhook, workflow } from '@/db/schema'
|
||||
|
||||
@@ -29,11 +30,13 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
workflow: {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
userId: workflow.userId,
|
||||
workspaceId: workflow.workspaceId,
|
||||
},
|
||||
})
|
||||
.from(webhook)
|
||||
.innerJoin(workflow, eq(webhook.workflowId, workflow.id))
|
||||
.where(and(eq(webhook.id, id), eq(workflow.userId, session.user.id)))
|
||||
.where(eq(webhook.id, id))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
@@ -41,6 +44,33 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const webhookData = webhooks[0]
|
||||
|
||||
// Check if user has permission to access this webhook
|
||||
let hasAccess = false
|
||||
|
||||
// Case 1: User owns the workflow
|
||||
if (webhookData.workflow.userId === session.user.id) {
|
||||
hasAccess = true
|
||||
}
|
||||
|
||||
// Case 2: Workflow belongs to a workspace and user has any permission
|
||||
if (!hasAccess && webhookData.workflow.workspaceId) {
|
||||
const userPermission = await getUserEntityPermissions(
|
||||
session.user.id,
|
||||
'workspace',
|
||||
webhookData.workflow.workspaceId
|
||||
)
|
||||
if (userPermission !== null) {
|
||||
hasAccess = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAccess) {
|
||||
logger.warn(`[${requestId}] User ${session.user.id} denied access to webhook: ${id}`)
|
||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Successfully retrieved webhook: ${id}`)
|
||||
return NextResponse.json({ webhook: webhooks[0] }, { status: 200 })
|
||||
} catch (error) {
|
||||
@@ -66,13 +96,14 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||
const body = await request.json()
|
||||
const { path, provider, providerConfig, isActive } = body
|
||||
|
||||
// Find the webhook and check ownership
|
||||
// Find the webhook and check permissions
|
||||
const webhooks = await db
|
||||
.select({
|
||||
webhook: webhook,
|
||||
workflow: {
|
||||
id: workflow.id,
|
||||
userId: workflow.userId,
|
||||
workspaceId: workflow.workspaceId,
|
||||
},
|
||||
})
|
||||
.from(webhook)
|
||||
@@ -85,9 +116,33 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
||||
return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (webhooks[0].workflow.userId !== session.user.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized webhook update attempt for webhook: ${id}`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
|
||||
const webhookData = webhooks[0]
|
||||
|
||||
// Check if user has permission to modify this webhook
|
||||
let canModify = false
|
||||
|
||||
// Case 1: User owns the workflow
|
||||
if (webhookData.workflow.userId === session.user.id) {
|
||||
canModify = true
|
||||
}
|
||||
|
||||
// Case 2: Workflow belongs to a workspace and user has write or admin permission
|
||||
if (!canModify && webhookData.workflow.workspaceId) {
|
||||
const userPermission = await getUserEntityPermissions(
|
||||
session.user.id,
|
||||
'workspace',
|
||||
webhookData.workflow.workspaceId
|
||||
)
|
||||
if (userPermission === 'write' || userPermission === 'admin') {
|
||||
canModify = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!canModify) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} denied permission to modify webhook: ${id}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
}
|
||||
|
||||
logger.debug(`[${requestId}] Updating webhook properties`, {
|
||||
@@ -136,13 +191,14 @@ export async function DELETE(
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Find the webhook and check ownership
|
||||
// Find the webhook and check permissions
|
||||
const webhooks = await db
|
||||
.select({
|
||||
webhook: webhook,
|
||||
workflow: {
|
||||
id: workflow.id,
|
||||
userId: workflow.userId,
|
||||
workspaceId: workflow.workspaceId,
|
||||
},
|
||||
})
|
||||
.from(webhook)
|
||||
@@ -155,12 +211,36 @@ export async function DELETE(
|
||||
return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (webhooks[0].workflow.userId !== session.user.id) {
|
||||
logger.warn(`[${requestId}] Unauthorized webhook deletion attempt for webhook: ${id}`)
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
|
||||
const webhookData = webhooks[0]
|
||||
|
||||
// Check if user has permission to delete this webhook
|
||||
let canDelete = false
|
||||
|
||||
// Case 1: User owns the workflow
|
||||
if (webhookData.workflow.userId === session.user.id) {
|
||||
canDelete = true
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0].webhook
|
||||
// Case 2: Workflow belongs to a workspace and user has write or admin permission
|
||||
if (!canDelete && webhookData.workflow.workspaceId) {
|
||||
const userPermission = await getUserEntityPermissions(
|
||||
session.user.id,
|
||||
'workspace',
|
||||
webhookData.workflow.workspaceId
|
||||
)
|
||||
if (userPermission === 'write' || userPermission === 'admin') {
|
||||
canDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!canDelete) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${session.user.id} denied permission to delete webhook: ${id}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
}
|
||||
|
||||
const foundWebhook = webhookData.webhook
|
||||
|
||||
// If it's a Telegram webhook, delete it from Telegram first
|
||||
if (foundWebhook.provider === 'telegram') {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getUserEntityPermissions } from '@/lib/permissions/utils'
|
||||
import { getOAuthToken } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/db'
|
||||
import { webhook, workflow } from '@/db/schema'
|
||||
@@ -94,18 +95,51 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Check if the workflow belongs to the user
|
||||
const workflows = await db
|
||||
.select({ id: workflow.id }) // Select only necessary field
|
||||
// Check if the workflow exists and user has permission to modify it
|
||||
const workflowData = await db
|
||||
.select({
|
||||
id: workflow.id,
|
||||
userId: workflow.userId,
|
||||
workspaceId: workflow.workspaceId,
|
||||
})
|
||||
.from(workflow)
|
||||
.where(and(eq(workflow.id, workflowId), eq(workflow.userId, userId)))
|
||||
.where(eq(workflow.id, workflowId))
|
||||
.limit(1)
|
||||
|
||||
if (workflows.length === 0) {
|
||||
logger.warn(`[${requestId}] Workflow not found or not owned by user: ${workflowId}`)
|
||||
if (workflowData.length === 0) {
|
||||
logger.warn(`[${requestId}] Workflow not found: ${workflowId}`)
|
||||
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const workflowRecord = workflowData[0]
|
||||
|
||||
// Check if user has permission to modify this workflow
|
||||
let canModify = false
|
||||
|
||||
// Case 1: User owns the workflow
|
||||
if (workflowRecord.userId === userId) {
|
||||
canModify = true
|
||||
}
|
||||
|
||||
// Case 2: Workflow belongs to a workspace and user has write or admin permission
|
||||
if (!canModify && workflowRecord.workspaceId) {
|
||||
const userPermission = await getUserEntityPermissions(
|
||||
userId,
|
||||
'workspace',
|
||||
workflowRecord.workspaceId
|
||||
)
|
||||
if (userPermission === 'write' || userPermission === 'admin') {
|
||||
canModify = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!canModify) {
|
||||
logger.warn(
|
||||
`[${requestId}] User ${userId} denied permission to modify webhook for workflow ${workflowId}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Check if a webhook with the same path already exists
|
||||
const existingWebhooks = await db
|
||||
.select({ id: webhook.id, workflowId: webhook.workflowId })
|
||||
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -392,8 +392,9 @@ export function WebhookModal({
|
||||
microsoftTeamsHmacSecret,
|
||||
])
|
||||
|
||||
// Use the provided path or generate a UUID-based path
|
||||
const formattedPath = webhookPath && webhookPath.trim() !== '' ? webhookPath : crypto.randomUUID()
|
||||
const formattedPath = useMemo(() => {
|
||||
return webhookPath && webhookPath.trim() !== '' ? webhookPath : crypto.randomUUID()
|
||||
}, [webhookPath])
|
||||
|
||||
// Construct the full webhook URL
|
||||
const baseUrl =
|
||||
|
||||
Reference in New Issue
Block a user