From 4dc2df4a12dc2c525a4e964902a6d2c52bcebdea Mon Sep 17 00:00:00 2001 From: Knee Cola Date: Mon, 29 Dec 2025 23:21:49 +0100 Subject: [PATCH] security: add server-side validation for email status transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement strict validation to prevent unauthorized email status changes: - Force status to Unverified when email address changes - Only allow client to reset status to Unverified (via reset button) - Block client from upgrading status (Unverified→Verified, etc.) - All status upgrades must happen server-side via verification links This prevents attackers from: - Submitting new emails with fake "verified" status - Bypassing email verification by modifying client requests - Escalating email status without proper verification flow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- web-app/app/lib/actions/locationActions.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/web-app/app/lib/actions/locationActions.ts b/web-app/app/lib/actions/locationActions.ts index 41f12ba..d548d2f 100644 --- a/web-app/app/lib/actions/locationActions.ts +++ b/web-app/app/lib/actions/locationActions.ts @@ -176,6 +176,20 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat }; } + // SECURITY: Validate email status transitions + // - If email changed: force to Unverified (prevents spoofing verified status) + // - If email unchanged: only allow client to reset to Unverified (via reset button) + // All other status transitions (Unverified→VerificationPending, VerificationPending→Verified) + // must happen server-side through other mechanisms (email verification links, etc.) + const emailHasChanged = currentLocation.tenantEmail !== (tenantEmail || null); + const clientWantsToReset = tenantEmailStatus === EmailStatus.Unverified; + + const finalEmailStatus = emailHasChanged + ? EmailStatus.Unverified // Email changed: force reset + : clientWantsToReset + ? EmailStatus.Unverified // Client initiated reset: allow it + : (currentLocation.tenantEmailStatus || EmailStatus.Unverified); // Otherwise: keep current status + // Handle different update scopes if (updateScope === "current" || !updateScope) { // Update only the current location (default behavior) @@ -194,7 +208,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat tenantTown: tenantTown || null, autoBillFwd: autoBillFwd || false, tenantEmail: tenantEmail || null, - tenantEmailStatus: tenantEmailStatus as EmailStatus || EmailStatus.Unverified, + tenantEmailStatus: finalEmailStatus, billFwdStrategy: billFwdStrategy || "when-payed", rentDueNotification: rentDueNotification || false, rentDueDay: rentDueDay || null, @@ -226,7 +240,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat tenantTown: tenantTown || null, autoBillFwd: autoBillFwd || false, tenantEmail: tenantEmail || null, - tenantEmailStatus: tenantEmailStatus as EmailStatus || EmailStatus.Unverified, + tenantEmailStatus: finalEmailStatus, billFwdStrategy: billFwdStrategy || "when-payed", rentDueNotification: rentDueNotification || false, rentDueDay: rentDueDay || null, @@ -251,7 +265,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat tenantTown: tenantTown || null, autoBillFwd: autoBillFwd || false, tenantEmail: tenantEmail || null, - tenantEmailStatus: tenantEmailStatus as EmailStatus || EmailStatus.Unverified, + tenantEmailStatus: finalEmailStatus, billFwdStrategy: billFwdStrategy || "when-payed", rentDueNotification: rentDueNotification || false, rentDueDay: rentDueDay || null,