StudyPlanner uses first-party email/password accounts in Cloudflare D1. Sessions are signed, stateless tokens stored in an HttpOnly cookie. Browser code receives a session-bound CSRF proof, but never the session token itself.
- Production cookies use
HttpOnly,Secure,SameSite=None, andPath=/. Local HTTP development usesSameSite=LaxwithoutSecure. - Deployed browsers call same-origin
/api/*on the Pages host so the cookie is first-party. Direct browser calls toworkers.devare not used: Safari/iOS treats that cookie as third-party and drops it. - Authenticated mutations require the
X-CSRF-Tokenheader. - A valid legacy bearer token is promoted once and then removed from local storage.
- Changing credentials increments the account session version, invalidates old sessions, and issues a replacement cookie for the current browser.
- Registration starts the existing session version at a cryptographically random positive 52-bit-range value instead of zero. Recreating a deleted username gets a fresh version, so old signed sessions are rejected (including legacy version zero). Values remain exactly representable through the D1 JavaScript bridge. Existing accounts/sessions are unchanged; no schema migration is needed.
- Logout clears the cookie and private per-user browser caches.
- Account deletion requires the current password, the exact confirmation
DELETE, and CSRF protection. It deletes cascade-owned account data in one D1 batch and expires the cookie. Data access requests are handled through the privacy contact route instead of a dedicated export API.
- Passwords use PBKDF2-SHA256 with a per-user random salt and are never logged.
AUTH_TOKEN_SECRETis a required Wrangler secret and must never be committed.ALLOWED_ORIGINSis an explicit allow-list; credentialed CORS never uses*.- Abuse-prone public endpoints use D1-backed fixed-window rate limits with hashed, non-reversible client keys.
- Diagnostics remove query strings and redact common credentials, headers, email addresses, transcripts, and grades. Entries are capped and old entries are removed during normal diagnostic requests.
- Security headers are configured in
frontend/public/_headersand backend responses.
The active D1 binding remains studyplanner-db
(80ca9092-ddc6-454a-b04a-8ccae85ef2f5). Run npm run db:verify-config before
deployment. Do not recreate or swap the database.