Accounts: email and password #37

Merged
anas merged 2 commits from feature/accounts into main 2026-10-08 21:05:05 +00:00
Owner

Closes #18. Step 2 of the plan: simple accounts, email address and password only, no emails and no SSO (owner decision).

Reader pages (plain forms, work without JavaScript)

  • /signup: email, password twice (8+ characters)
  • /signin: returns to the page the reader came from (?next=, same-site paths only)
  • /account: sign out, change password (signs out other devices), delete account (password required; removes the account and its data)
  • Header: "لاگ ان" or "اکاؤنٹ"; "forgot password? ask an admin" (admin reset comes in #27)

Security

  • Passwords: scrypt (N=32768, r=8, p=1) with a per-user salt, stored as scrypt$N$r$p$salt$hash so the cost can be raised later; unknown emails take as long as wrong passwords.
  • Sessions: a random 32-byte token in an HTTP-only, SameSite=Lax cookie (Secure over HTTPS); the database stores only its SHA-256; 30 days, renewed while in use.
  • Rate limits: sign-in 8 per email and 20 per IP per 15 minutes; sign-up 5 per IP per hour (in-process memory, fine for one API process).
  • CSRF: Astro's origin check. Astro only trusts the Host header for listed hostnames, so the site's hostnames are configured with SITE_HOSTS (build time, default 127.0.0.1,localhost). The production domain must be added at deploy, or sign-in forms get 403.

Database: users, sessions (in db/schema.sql, applied by the import or psql < db/schema.sql).

Tested

  • npm test: 19 pass, including the HTTP flow (sign-up, duplicate email, sign-in, wrong password, change password signs out other sessions, sign-out, delete) and the per-email rate limit.
  • Against the running site: sign-up sets an HttpOnly cookie and shows "اکاؤنٹ"; cross-site posts to sign-up and change-password are refused (403); wrong and mismatched passwords show Urdu errors; ?next= works and //evil.example is ignored; deleting the account leaves no rows.

Independent of #26 (selection menu); either can merge first.

🤖 Generated with Claude Code

Closes #18. Step 2 of the plan: simple accounts, email address and password only, no emails and no SSO (owner decision). **Reader pages** (plain forms, work without JavaScript) - `/signup`: email, password twice (8+ characters) - `/signin`: returns to the page the reader came from (`?next=`, same-site paths only) - `/account`: sign out, change password (signs out other devices), delete account (password required; removes the account and its data) - Header: "لاگ ان" or "اکاؤنٹ"; "forgot password? ask an admin" (admin reset comes in #27) **Security** - Passwords: scrypt (N=32768, r=8, p=1) with a per-user salt, stored as `scrypt$N$r$p$salt$hash` so the cost can be raised later; unknown emails take as long as wrong passwords. - Sessions: a random 32-byte token in an HTTP-only, SameSite=Lax cookie (Secure over HTTPS); the database stores only its SHA-256; 30 days, renewed while in use. - Rate limits: sign-in 8 per email and 20 per IP per 15 minutes; sign-up 5 per IP per hour (in-process memory, fine for one API process). - CSRF: Astro's origin check. Astro only trusts the Host header for listed hostnames, so the site's hostnames are configured with **`SITE_HOSTS`** (build time, default `127.0.0.1,localhost`). **The production domain must be added at deploy**, or sign-in forms get 403. **Database**: `users`, `sessions` (in `db/schema.sql`, applied by the import or `psql < db/schema.sql`). **Tested** - `npm test`: 19 pass, including the HTTP flow (sign-up, duplicate email, sign-in, wrong password, change password signs out other sessions, sign-out, delete) and the per-email rate limit. - Against the running site: sign-up sets an HttpOnly cookie and shows "اکاؤنٹ"; cross-site posts to sign-up and change-password are refused (403); wrong and mismatched passwords show Urdu errors; `?next=` works and `//evil.example` is ignored; deleting the account leaves no rows. Independent of #26 (selection menu); either can merge first. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
anas added 1 commit 2026-10-08 20:54:05 +00:00
- API (api/src/auth.ts): scrypt password hashes; random session tokens stored only as SHA-256;
  30-day sliding sessions; rate limits on sign-in (per IP and per email) and sign-up; changing the
  password signs out other devices; deleting the account removes its data.
- Site: /signup, /signin (returns to the page the reader came from), /account; header link; plain
  forms, no JavaScript needed. Session in an HTTP-only, SameSite=Lax cookie (Secure over HTTPS).
- CSRF: Astro's origin check, with the site's hostnames listed (SITE_HOSTS) so its own form posts
  pass and other sites' are refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anas added 1 commit 2026-10-08 20:58:11 +00:00
anas merged commit 6610ed0f71 into main 2026-10-08 21:05:05 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: anas/divan#37
No description provided.