diff --git a/docs/plans/2026-02-28-remember-me-multi-device-design.md b/docs/plans/2026-02-28-remember-me-multi-device-design.md new file mode 100644 index 0000000..52da6d6 --- /dev/null +++ b/docs/plans/2026-02-28-remember-me-multi-device-design.md @@ -0,0 +1,57 @@ +# Remember-Me Multi-Device Tokens Design (2026-02-28) + +## Context +After the security changes on 2026-02-26, remember-me uses a single `users.remember_token` value. This breaks multi-browser usage because each login overwrites the previous token. The goal is to allow unlimited concurrent remember-me sessions across devices and browsers, with retention cleanup for tokens older than 6 months. + +## Goals +- Allow unlimited concurrent remember-me sessions per user. +- Store only hashed tokens in the database. +- Keep existing cookie security attributes (`Secure`, `HttpOnly`, `SameSite=Lax`). +- Clean up tokens older than 6 months. + +## Non-Goals +- Reworking the entire authentication system. +- Introducing refresh-token or JWT flows. + +## Data Model +Add a new table `users_remember_tokens`: +- `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY +- `user_id` INT UNSIGNED NOT NULL, indexed +- `token_hash` CHAR(64) NOT NULL, unique +- `created_at` DATETIME NOT NULL +- `last_used_at` DATETIME NULL +- `user_agent` VARCHAR(255) NULL +- `ip` VARCHAR(45) NULL + +Rationale: +- Store only `token_hash` (SHA-256 of raw token) to avoid storing bearer tokens in plain text. +- Use `last_used_at` for retention and auditing. If null, fall back to `created_at`. + +## Login Flow +1. When user logs in with "Zapamiêtaj mnie": + - Generate random token (64 hex). + - Store `hash = sha256(token)` in `users_remember_tokens` with metadata and timestamps. + - Set cookie to the raw token with expiry +1 year and existing security attributes. +2. When user logs in without "Zapamiêtaj mnie": + - Do not create a token. + - Existing cookie is cleared. + +## Auto-Login Flow +1. If no session and cookie exists: + - Hash cookie value with SHA-256. + - Look up `users_remember_tokens` by `token_hash`. + - If found, load user, set session, update `last_used_at`. + - If not found, clear cookie. + +## Retention +- Delete tokens older than 6 months based on `last_used_at` (or `created_at` if `last_used_at` is null). +- Trigger cleanup during login and auto-login paths to avoid extra jobs. + +## Error Handling +- Missing or invalid token in DB results in clearing the cookie and continuing to login screen. +- No user row for token is treated as invalid and cleaned. + +## Testing +- Integration test: login with remember-me sets cookie and inserts token row. +- Integration test: auto-login succeeds from cookie after session is cleared. +- Retention test: tokens older than 6 months are removed on login.