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_VALIDATIONhandler to the global error handler inserver.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: trueto 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 guard —
beforeunloadlistener 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/:idfor single report deletion - Added
DELETE /admin/bug-reportswith{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