CitySchedule
Version 1.4 · July 2026
- Application: Next.js on Vercel (US regions)
- Database: Supabase (PostgreSQL) on AWS US-East-1
- File Storage: Supabase Storage (S3-compatible) on AWS US-East-1
- Authentication: Supabase Auth (SOC 2 Type II)
- Email: Resend (US-based transactional email)
- Data residency: All resident data and files stored on US soil
- Uptime SLA: 99.9% via Vercel and Supabase infrastructure
- In transit: TLS 1.2+ enforced on all endpoints; HTTPS-only
- At rest: AES-256 encryption (Supabase managed)
- PII collected: Full name, email, phone number, and (only for services that require it) a site address; no SSNs or payment data
- Files: Resident attachments and inspector field photos stored in isolated cloud storage buckets, distinguished by file kind. All downloads require authenticated request + cryptographically signed URL (1-hour expiration). File access validated against reservation ownership.
- File upload validation: Server-side MIME-type allowlist (JPEG, PNG, GIF, WebP, PDF, Word docs); 10 MB size limit; filenames sanitized; no executable types accepted
- Data retention: Records retained for 3 years after reservation close. Deletions in the app are soft deletes: records are hidden from view but preserved for municipal records-retention compliance. Cities may request deletion at any time.
- Log archival: Audit logs, status history, and inspection-record detail tables are partitioned by year; partitions older than the current and two prior years are exported to write-once (immutable) archive storage and removed from the live database
- Backups: Automated daily backups via Supabase; encrypted at rest
- SQL injection: Prevented via parameterized queries (Supabase client library)
- Cross-site scripting (XSS): React automatically escapes JSX output; email templates use explicit HTML escaping for all user input
- CSV injection: Exported files do not include formulas
- HTML entity handling: Special characters (<, >, &, ") are escaped in all user-controlled output
- Ownership validation: Residents can only upload/download files for their own reservations; city staff can access files for any reservation in their city
- Path validation: File download requests must reference a path under the reservation ID; cross-reservation path traversal attempts are rejected (403 Forbidden)
- Storage isolation: Each reservation has its own storage directory; bucket-level policies prevent direct public access
- Supabase — SOC 2 Type II (authentication, database, storage)
- CCPA / GDPR: See our Privacy Policy for resident rights and data handling
- Testing: Automated unit and integration tests validate access controls, data formatting, and error handling
Authentication is handled by Supabase Auth. Features:
- Email/password and OAuth 2.0 (Google)
- SSO support (SAML 2.0) available on Supabase enterprise plans
- Mandatory multi-factor authentication (MFA): every account must enroll a TOTP (authenticator-app) second factor. There are no exemptions. Enrollment and AAL2 step-up are enforced server-side in the proxy on every page and API request, and the gate fails closed: a user who has not stepped up (or whose assurance level cannot be read) cannot reach protected resources.
- MFA recovery: lost-device recovery is an administrator-initiated reset of the user's enrolled factors (audited, super-admin only); the user must re-enroll a new second factor before regaining access. There is no email-only bypass of the second factor.
- Bot protection: Cloudflare Turnstile captcha on sign-in, sign-up, and password-reset; tokens verified server-side by Supabase Auth before credentials are accepted
- Session management with automatic token refresh
Authorization is enforced at the API level on every request. Roles and city assignments are checked before any data access. Postgres row-level security (RLS) policies provide a second, database-level enforcement layer on data reads, so a bypassed or buggy API check alone cannot expose another user's rows:
| Role | Access scope | Restrictions |
|---|---|---|
| Resident | Own reservations | Cannot edit approved/denied reservations; cannot see other residents |
| Staff | Reservations in their city | Cannot delete inspection records; cannot manage staff; cannot access other cities |
| Admin | Reservations + staff in their city | Cannot modify other cities; cannot grant super-admin role |
| Super Admin | All cities + system config | CitySchedule staff only; enforced at infrastructure level |
City isolation is enforced on every API call: staff/admins cannot access data from cities where they are not assigned. Super admins can view all cities but their access is logged.
No privilege escalation: Roles are assigned by city administrators; users cannot modify their own role. Role grants are logged for audit purposes.
Job-function capabilities (such as inspector or plan reviewer) are a separate dimension from the access tier, managed only through the service client. Inspection outcomes and field photos can be recorded only against an already-approved appointment, and only by the staff member the appointment is assigned to, not by capability alone.
- Automated tests: Unit tests for authorization helpers, time formatting, and data validation. Integration tests for core reservation flows.
- E2E smoke tests: Playwright tests validate public pages render correctly and unauthenticated users are redirected from protected routes
- Type safety: Full TypeScript coverage; all API parameters validated at compile time
- Linting: ESLint checks for common mistakes (unescaped entities, missing dependency arrays)
- Audit logging: All reservation actions (create, update, approve, deny) logged with user ID, timestamp, and changed fields. Logs are PII-free by design: entries record field names and outcomes, never field contents; API access logs record only the method and URL path (no query strings, bodies, or headers).
- RLS test harness: Row-level security policies are replayed against the real migrations and exercised by an automated test suite on an embedded Postgres, validating cross-city and cross-user isolation at the database layer
- Rate limiting: Per-identity limits enforced in the proxy on every API request — a global ceiling, a stricter ceiling for all mutating (write) requests, and a tight dedicated bucket for reservation booking. Requests over the limit receive HTTP 429.
- Cross-origin protection: API requests carrying a foreign
Originare rejected with 403; only same-origin and explicitly allowlisted origins may call the API. CORS preflight is handled accordingly. - Captcha: Cloudflare Turnstile gates the authentication forms against automated credential-stuffing and signup abuse
- MFA enforcement: All accounts are gated at the proxy on both pages and API routes; an AAL1 session cannot bypass the second-factor prompt, and the gate fails closed
- Security contact: security@cityschedule.app (monitored 24/7)
- Breach notification: Affected residents and city staff notified within 72 hours of confirmed incident, with description of exposure and remediation steps
- Vulnerability disclosure: We welcome responsible disclosure. Please email security concerns rather than posting publicly.
- Remediation SLA: Critical vulnerabilities patched within 24 hours; high-severity within 7 days
- Supabase: Authentication, database, and file storage; SOC 2 Type II; data encrypted at rest
- Resend: Transactional email only (confirmations, status updates); no sensitive PII in email subject lines; no resident lists sent to Resend
- Cloudflare: Turnstile captcha for bot/abuse protection on authentication forms; receives browser challenge signals (including IP address) only at solve time; no resident reservation data shared
- Vercel: Application hosting; automatic DDoS protection; edge caching
- Data sharing: No resident data is ever shared, sold, or rented to third parties except as required by law