v3.3.4 5 February 2026

Password Security Alignment & Error Handling

Password Validation — Single Source of Truth

Password rules were inconsistent across four endpoints. Registration accepted passwords without letters or numbers. Password change and reset required 12 characters instead of 8. Frontend and backend rules disagreed.

Fix

  • Created api/src/lib/password-rules.ts — one shared Zod schema and Fastify JSON schema
  • All four consumers now import from this single file: registration, password change, password reset, frontend
  • Standardised on 8+ characters, at least one letter, at least one number
  • Aligned Fastify JSON Schema fast-reject with Zod source of truth

Rules (all endpoints)

  • Minimum 8 characters
  • At least one letter (a-z or A-Z)
  • At least one number (0-9)
  • Argon2id hashing (64MB memory, timeCost 3, parallelism 4)

Fastify Schema Validation Error Handler

Requests rejected by Fastify’s JSON Schema layer (before Zod runs) returned a generic INTERNAL_ERROR instead of a formatted VALIDATION_ERROR with field-level details. This affected all routes globally.

Fix

  • Added FST_ERR_VALIDATION handler to the global error handler in server.ts
  • Schema validation errors now return the same {success, error: {code, message, details}} envelope as Zod errors
  • Frontend, API clients, and DevShell all handle them identically

Password Reset Error Response Fix

The password reset endpoint’s response schema defined error as { type: 'object' } without additionalProperties: true. Fastify’s fast serializer stripped all properties, returning error: {} with no code, message, or details.

Fix

  • Added additionalProperties: true to the 400 response schema on /auth/reset-password
  • Error details now pass through correctly

Registration Page — Final Polish

  • Header layout — logo top-left, step indicator right-aligned and de-emphasised
  • Dirty form guardbeforeunload listener warns on navigation if any field has content
  • Sole Trader context — org field label changes to “Trading Name (optional)” with hint “Shown on invoices and reports”
  • CTA sub-text — footnote-sized “90-day free trial · No charge today · Takes ~30 seconds”
  • Plan hover state — stronger contrast on inactive plan button
  • Password placeholder — “8+ characters, one letter, one number” (matches actual rules)
  • Developer placeholder — “e.g. ByteStack Freelance” for sole trader trading name

Admin Bug Reports — Delete Functionality

  • Added DELETE /admin/bug-reports/:id for single report deletion
  • Added DELETE /admin/bug-reports with {ids: [...]} or {all: true} for bulk deletion
  • Detail panel now shows trash icon for quick delete
  • Page header shows “Delete All” button when reports exist
  • Both include confirmation dialogs

Registration — Stripe Redirect Fix

The “Leave site?” browser popup fired when redirecting to Stripe Checkout because the form was marked dirty. Changed isRedirecting from useState to useRef so the flag updates synchronously before the redirect.

RegisterSuccessPage

New post-Stripe success page at /register/success:

  • Shows trial status confirmation
  • Displays session ID for reference
  • Auto-redirects to dashboard after 5 seconds (if logged in)
  • Falls back to “Sign In” link if session expired

Tags: security bugfix improvement

Components: API, Application