From 08d996781a3011cccdc14f20265d5b1078081c98 Mon Sep 17 00:00:00 2001 From: willchen96 Date: Thu, 14 May 2026 23:29:08 +0800 Subject: [PATCH] feat: enhance workflow sharing by preventing users from sharing with themselves and normalizing email inputs --- backend/src/routes/workflows.ts | 21 +++++++++++++++++-- .../workflows/ShareWorkflowModal.tsx | 15 +++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/workflows.ts b/backend/src/routes/workflows.ts index 83ae451..41ddfd0 100644 --- a/backend/src/routes/workflows.ts +++ b/backend/src/routes/workflows.ts @@ -370,10 +370,27 @@ workflowsRouter.delete("/:workflowId/shares/:shareId", requireAuth, asyncRoute(a // POST /workflows/:workflowId/share workflowsRouter.post("/:workflowId/share", requireAuth, asyncRoute(async (req, res) => { const userId = res.locals.userId as string; + const userEmail = res.locals.userEmail as string | undefined; const { workflowId } = req.params; const { emails, allow_edit } = req.body as { emails: string[]; allow_edit: boolean }; if (!emails?.length) return void res.status(400).json({ detail: "emails is required" }); + const normalizedEmails = [ + ...new Set( + emails + .map((email) => email.trim().toLowerCase()) + .filter(Boolean), + ), + ]; + if (normalizedEmails.length === 0) { + return void res.status(400).json({ detail: "emails is required" }); + } + const normalizedUserEmail = userEmail?.trim().toLowerCase(); + if (normalizedUserEmail && normalizedEmails.includes(normalizedUserEmail)) { + return void res + .status(400) + .json({ detail: "You cannot share a workflow with yourself." }); + } const db = createServerSupabase(); // Verify ownership @@ -386,10 +403,10 @@ workflowsRouter.post("/:workflowId/share", requireAuth, asyncRoute(async (req, r .single(); if (!wf) return void res.status(404).json({ detail: "Workflow not found or not editable" }); - const rows = emails.map((email: string) => ({ + const rows = normalizedEmails.map((email: string) => ({ workflow_id: workflowId, shared_by_user_id: userId, - shared_with_email: email.trim().toLowerCase(), + shared_with_email: email, allow_edit: allow_edit ?? false, })); // Upsert on (workflow_id, shared_with_email) so re-sharing to the same diff --git a/frontend/src/app/components/workflows/ShareWorkflowModal.tsx b/frontend/src/app/components/workflows/ShareWorkflowModal.tsx index 5144132..bfca0df 100644 --- a/frontend/src/app/components/workflows/ShareWorkflowModal.tsx +++ b/frontend/src/app/components/workflows/ShareWorkflowModal.tsx @@ -8,6 +8,7 @@ import { listWorkflowShares, shareWorkflow, } from "@/app/lib/mikeApi"; +import { useAuth } from "@/contexts/AuthContext"; import { EmailPillInput } from "../shared/EmailPillInput"; interface Share { @@ -33,6 +34,8 @@ export function ShareWorkflowModal({ const [existingShares, setExistingShares] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); + const { user } = useAuth(); + const ownEmail = user?.email?.trim().toLowerCase() ?? null; useEffect(() => { listWorkflowShares(workflowId) @@ -47,10 +50,13 @@ export function ShareWorkflowModal({ } async function handleConfirm() { - if (pendingEmails.length === 0) return; + const emails = ownEmail + ? pendingEmails.filter((email) => email !== ownEmail) + : pendingEmails; + if (emails.length === 0) return; setSaving(true); try { - await shareWorkflow(workflowId, { emails: pendingEmails, allow_edit: allowEdit }); + await shareWorkflow(workflowId, { emails, allow_edit: allowEdit }); const updated = await listWorkflowShares(workflowId); setExistingShares(updated); setPendingEmails([]); @@ -84,6 +90,11 @@ export function ShareWorkflowModal({ + ownEmail && email === ownEmail + ? "You cannot share a workflow with yourself." + : null + } placeholder="Add people by email…" autoFocus />