Authentication
- Powered by Supabase Auth (JWT-based).
- Passwords are hashed with bcrypt before storage; plaintext is never stored.
- Recovery links use signed, time-limited tokens (1 hour expiry).
- Sessions use secure HTTP-only cookies.
Authorization
- Role-based with granular permission keys.
- Enforced at three layers:
- UI — nav items hidden if you don’t have the permission
- Server actions — every action calls
requirePermission()or similar before running - Row Level Security (RLS) — Postgres policies check permissions inside the database, so even a compromised client can’t bypass them
Data at rest
- Supabase Postgres with encryption at rest (AES-256 via AWS RDS).
- SMTP passwords are additionally encrypted with AES-256-GCM using a key held only in Vercel’s environment variables (
IW_ENCRYPTION_KEY). The encrypted ciphertext includes an authentication tag — any tampering is detected on decrypt. - No secrets in git —
.gitignoreexcludes.env*, and Vercel’s “Sensitive” flag prevents env values from appearing invercel env pulloutput.
Data in transit
- All traffic is HTTPS (Vercel enforces TLS 1.2+ on
.vercel.app). - SMTP uses STARTTLS (port 587) or SSL/TLS (port 465) — never plaintext.
- Server-to-Supabase uses TLS.
Secrets management
| Where | What’s stored |
|---|---|
| Vercel env vars (Sensitive) | Supabase service-role key, encryption key |
| Vercel env vars (public) | Supabase URL, anon key (NEXT_PUBLIC_*) |
| iWorkWhen DB (encrypted) | SMTP password ciphertext |
| Never committed | Actual env values |
What’s logged
- Audit log — all admin/sensitive actions (see Audit log)
- Vercel logs — HTTP request logs, function logs (retained ~30 days on Pro)
- Supabase logs — DB queries if enabled (off by default for performance)
What’s not logged
- User passwords (anywhere, ever)
- SMTP passwords (only the encrypted ciphertext)
- Personal info beyond what’s in the profiles table
- Individual message bodies (in external logs — they’re in the DB)
Threat model
iWorkWhen is designed to protect against:- External attackers — no public endpoints bypass auth; all reads/writes go through RLS
- Curious coworkers — roles and permissions limit sideways access; you can only see what your role permits
- Compromised client — RLS prevents a stolen JWT from reading data the user’s role wouldn’t permit
- Accidental exposure — encrypted storage for SMTP, gitignored env files, no secrets in logs
- A compromised admin account — Admin has all permissions by design. Treat admin credentials with care.
- A compromised Vercel or Supabase account — those are your infrastructure root.
Best practices
Rotate the admin password
Change the default admin password immediately after install. Don’t share it.
Use an app password for SMTP
If your Exchange mailbox has MFA, create a dedicated app password for iWorkWhen — don’t reuse the mailbox password.
Regularly review the audit log
Weekly review catches anomalies early.
Prune disabled users
Disable users who leave immediately. They can’t sign in, but old sessions might still be valid briefly.