From 2ae09149a5f42da22c35e376ab914f22adda34d3 Mon Sep 17 00:00:00 2001 From: Corey Blais Date: Thu, 16 Apr 2026 21:20:17 -0400 Subject: [PATCH] Fix subscription write error --- backend/src/app.ts | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/backend/src/app.ts b/backend/src/app.ts index 68c11bb..fdcaf1d 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -947,6 +947,11 @@ const requireWorkspaceRole = (allowedRoles: WorkspaceRole[]) => (req: Request, r next(); }; +const isBillingOnlyWorkspaceUpdate = ( + workspace: WorkspaceRow, + payload: z.infer, +) => workspace.workspace_type === 'standard' && payload.workspaceType === 'standard' && payload.name === workspace.name; + app.get('/api/health', (_req: Request, res: Response) => { res.json({ ok: true }); }); @@ -1514,7 +1519,7 @@ app.get('/api/workspace', requireAuth, async (req: Request, res: Response) => { res.json({ workspace: normalizeWorkspace(req.auth!.workspace) }); }); -app.put('/api/workspace', requireAuth, requireWriteAccess, requireWorkspaceRole(['owner', 'assistant']), async (req: Request, res: Response, next: NextFunction) => { +app.put('/api/workspace', requireAuth, requireSessionAuth, requireWorkspaceRole(['owner', 'assistant']), async (req: Request, res: Response, next: NextFunction) => { const parsed = workspaceSchema.safeParse(req.body); if (!parsed.success) { @@ -1523,17 +1528,35 @@ app.put('/api/workspace', requireAuth, requireWriteAccess, requireWorkspaceRole( } try { - const billingPlan = resolveBillingPlan(parsed.data.workspaceType, parsed.data.billingPlan ?? req.auth!.workspace.billing_plan); + const currentWorkspace = req.auth!.workspace; + const billingPlan = resolveBillingPlan(parsed.data.workspaceType, parsed.data.billingPlan ?? currentWorkspace.billing_plan); + const billingInterval = parsed.data.workspaceType === 'rescue' ? 'monthly' : (parsed.data.billingInterval ?? currentWorkspace.billing_interval); + const canUpdateWorkspace = + isAdminUser(req.auth!.user) || + subscriptionAllowsWrite(currentWorkspace) || + isBillingOnlyWorkspaceUpdate(currentWorkspace, parsed.data); + + if (!canUpdateWorkspace) { + res.status(402).json({ + error: + currentWorkspace.workspace_type === 'rescue' + ? 'This rescue flock is read-only until FlockPal verifies it.' + : 'This flock is read-only until the subscription is restored.', + code: 'workspace_read_only', + }); + return; + } + const workspace = await updateWorkspace({ - workspaceId: req.auth!.workspace.id, + workspaceId: currentWorkspace.id, name: parsed.data.name, workspaceType: parsed.data.workspaceType, billingEmail: emptyToNull(parsed.data.billingEmail), billingPlan, - billingInterval: parsed.data.workspaceType === 'rescue' ? 'monthly' : (parsed.data.billingInterval ?? req.auth!.workspace.billing_interval), + billingInterval, }); - if (workspace?.workspace_type === 'rescue' && req.auth!.workspace.workspace_type !== 'rescue') { + if (workspace?.workspace_type === 'rescue' && currentWorkspace.workspace_type !== 'rescue') { await sendRescueStatusNotification({ workspace, ownerEmail: req.auth!.user.email,