|
| 1 | +# Feature Plan 023 – Remember Me Login |
| 2 | + |
| 3 | +_Linked specification:_ `docs/specs/4-architecture/features/023-remember-me-login/spec.md` |
| 4 | +_Status:_ Draft |
| 5 | +_Last updated:_ 2026-02-28 |
| 6 | + |
| 7 | +> Guardrail: Keep this plan traceable back to the governing spec. Reference FR/NFR/Scenario IDs from `spec.md` where relevant, log any new high- or medium-impact questions in [docs/specs/4-architecture/open-questions.md](../../open-questions.md), and assume clarifications are resolved only when the spec's normative sections have been updated. |
| 8 | +
|
| 9 | +## Vision & Success Criteria |
| 10 | + |
| 11 | +**User value:** Users can check a "Remember Me" box during login to stay authenticated across browser restarts and session expiry, eliminating the need to re-enter credentials on every visit to their Lychee instance. |
| 12 | + |
| 13 | +**Success signals:** |
| 14 | +- Login with "Remember Me" checked sets a long-lived remember cookie that survives browser restart. |
| 15 | +- Login without "Remember Me" (or with absent field) behaves identically to the current implementation. |
| 16 | +- Logout properly invalidates the remember cookie and rotates the `remember_token`. |
| 17 | +- LDAP login with "Remember Me" works identically to local login. |
| 18 | +- All existing login tests pass without modification (backward compatibility). |
| 19 | +- Frontend checkbox visible only when basic auth is enabled. |
| 20 | + |
| 21 | +## Scope Alignment |
| 22 | + |
| 23 | +- **In scope:** |
| 24 | + - `LoginRequest` validation: add optional `remember_me` boolean field. |
| 25 | + - `RequestAttribute` constant: `REMEMBER_ME_ATTRIBUTE = 'remember_me'`. |
| 26 | + - `AuthController::login()`: pass `remember` flag to `Auth::attempt()` (local) and `Auth::login()` (LDAP). |
| 27 | + - `auth-service.ts`: send `remember_me` in POST body. |
| 28 | + - `LoginForm.vue`: add checkbox with `remember_me` ref, pass to login service. |
| 29 | + - Translation strings for "Remember me" in 22 languages. |
| 30 | + - Feature tests for remember cookie presence/absence. |
| 31 | + |
| 32 | +- **Out of scope:** |
| 33 | + - Admin-configurable remember duration via settings UI (use `REMEMBER_LIFETIME` env variable; default 4 weeks). |
| 34 | + - WebAuthn/OAuth remember-me support. |
| 35 | + - Remember-me for the migration/setup authentication flow. |
| 36 | + - Session lifetime configuration changes. |
| 37 | + |
| 38 | +## Dependencies & Interfaces |
| 39 | + |
| 40 | +- **Laravel SessionGuard** — Provides the remember cookie mechanism. `SessionOrTokenGuard` extends this and already accepts `$remember` in `login()`. |
| 41 | +- **`remember_token` column** — Already exists in the `users` table (Laravel default migration). No new migration needed. |
| 42 | +- **`SessionOrTokenGuard::login()`** — Already accepts `$remember = false` parameter (line 274). Just needs to be called with `true`. |
| 43 | +- **PrimeVue Checkbox** — Use PrimeVue's `Checkbox` component for the login form. |
| 44 | +- **Translation system** — 22 language files under `lang/<locale>/`. |
| 45 | + |
| 46 | +## Assumptions & Risks |
| 47 | + |
| 48 | +- **Assumptions:** |
| 49 | + - The `remember_token` column in the `users` table is functional (Laravel default). |
| 50 | + - `SessionOrTokenGuard`'s `recaller()` method (inherited from Laravel's `SessionGuard`) correctly handles remember-me cookies. |
| 51 | + - The `remember` duration defaults to 4 weeks (40320 minutes), set via `config/auth.php` guard config with `REMEMBER_LIFETIME` env override (Q-023-01 resolved → Option C). |
| 52 | + |
| 53 | +- **Risks / Mitigations:** |
| 54 | + - **R1: Remember cookie not set due to guard misconfiguration.** Mitigation: Write a feature test that asserts the cookie is present in the response. |
| 55 | + - **R2: LDAP users may not persist `remember_token` correctly.** Mitigation: LDAP users are provisioned as local `User` records which have the `remember_token` column. Test explicitly. |
| 56 | + - **R3: Checkbox accessibility.** Mitigation: Use PrimeVue's accessible `Checkbox` component with proper `aria-label` and `id`/`label` binding. |
| 57 | + |
| 58 | +## Implementation Drift Gate |
| 59 | + |
| 60 | +After each increment, verify: |
| 61 | +1. `make phpstan` — Zero errors |
| 62 | +2. `php artisan test` — All tests pass |
| 63 | +3. `vendor/bin/php-cs-fixer fix --dry-run` — Code style clean |
| 64 | +4. `npm run check` — TypeScript/Vue checks pass |
| 65 | +5. Check `tasks.md` checkboxes match actual progress |
| 66 | + |
| 67 | +Record drift findings in this plan's Follow-ups section. |
| 68 | + |
| 69 | +## Increment Map |
| 70 | + |
| 71 | +### I1 – Backend: LoginRequest + AuthController + RequestAttribute (~45 min) |
| 72 | + |
| 73 | +- _Goal:_ Wire the `remember_me` parameter through the backend login flow. |
| 74 | +- _Preconditions:_ Clean test suite, understanding of `SessionOrTokenGuard` remember behavior. |
| 75 | +- _Steps:_ |
| 76 | + 1. Write failing test: POST `/Auth::login` with `remember_me = true` → verify remember cookie in response. |
| 77 | + 2. Write failing test: POST `/Auth::login` with `remember_me = false` → verify no remember cookie. |
| 78 | + 3. Write failing test: POST `/Auth::login` without `remember_me` → verify no remember cookie (backward compat). |
| 79 | + 4. Add `REMEMBER_ME_ATTRIBUTE = 'remember_me'` to `RequestAttribute`. |
| 80 | + 5. Add `HasRememberMe` contract interface and `HasRememberMeTrait` trait (or add directly to `LoginRequest`), with validation rule: `'remember_me' => ['sometimes', 'boolean']` defaulting to `false`. |
| 81 | + 6. Update `AuthController::login()`: |
| 82 | + - Read `$remember = $request->remember()` (or similar accessor). |
| 83 | + - Pass to `Auth::attempt([...], $remember)` for local auth. |
| 84 | + - Pass to `Auth::login($user, $remember)` for LDAP auth. |
| 85 | + 7. Update login log messages to include remember flag. |
| 86 | + 8. Run `make phpstan` and `php artisan test`. |
| 87 | +- _Commands:_ `make phpstan`, `XDEBUG_MODE=off php artisan test --no-coverage` |
| 88 | +- _Exit:_ Login with `remember_me = true` sets remember cookie. Login without or with `false` does not. All existing tests green. |
| 89 | +- _Refs:_ FR-023-01, FR-023-04, FR-023-05, FR-023-06, S-023-01 through S-023-08, S-023-11, NFR-023-02, NFR-023-03, NFR-023-04 |
| 90 | + |
| 91 | +### I2 – Frontend: Checkbox + Auth Service (~45 min) |
| 92 | + |
| 93 | +- _Goal:_ Add the "Remember Me" checkbox to the login form and wire it to the API. |
| 94 | +- _Preconditions:_ I1 complete (backend accepts `remember_me`). |
| 95 | +- _Steps:_ |
| 96 | + 1. Update `auth-service.ts`: add `remember_me` parameter to `login()` method. |
| 97 | + 2. Update `LoginForm.vue`: |
| 98 | + - Add a `remember_me` ref (default `false`). |
| 99 | + - Add PrimeVue `Checkbox` below the password field, bound to `remember_me`. |
| 100 | + - Pass `remember_me.value` to `AuthService.login()`. |
| 101 | + - Checkbox only rendered inside the `v-if="is_basic_auth_enabled"` block. |
| 102 | + 3. Verify checkbox defaults to unchecked. |
| 103 | + 4. Verify checkbox hidden when basic auth is not enabled. |
| 104 | + 5. Run `npm run check` and `npm run format`. |
| 105 | +- _Commands:_ `npm run check`, `npm run format` |
| 106 | +- _Exit:_ Login form shows "Remember Me" checkbox. Checking it sends `remember_me: true` to the backend. |
| 107 | +- _Refs:_ FR-023-02, FR-023-03, S-023-09, S-023-10, UI-023-01, UI-023-02, UI-023-03 |
| 108 | + |
| 109 | +### I3 – Translations (~30 min) |
| 110 | + |
| 111 | +- _Goal:_ Add translation strings for "Remember me" in all 22 supported languages. |
| 112 | +- _Preconditions:_ I2 complete (knows the translation key needed). |
| 113 | +- _Steps:_ |
| 114 | + 1. Add English translation: `'remember_me' => 'Remember me'` in the appropriate dialog/login section of `lang/en/lychee.php` (or equivalent). |
| 115 | + 2. Add placeholder translations for all 21 other languages using the English string. |
| 116 | + 3. Run `php artisan test` to verify translations don't break. |
| 117 | +- _Commands:_ `XDEBUG_MODE=off php artisan test --no-coverage`, `vendor/bin/php-cs-fixer fix --dry-run` |
| 118 | +- _Exit:_ Translation key available in all 22 languages. |
| 119 | +- _Refs:_ NFR-023-05 |
| 120 | + |
| 121 | +### I4 – Integration Tests & Cleanup (~30 min) |
| 122 | + |
| 123 | +- _Goal:_ End-to-end verification and documentation updates. |
| 124 | +- _Preconditions:_ I1, I2, I3 complete. |
| 125 | +- _Steps:_ |
| 126 | + 1. End-to-end test: login with remember → close session → request with remember cookie → authenticated. |
| 127 | + 2. End-to-end test: login with remember → logout → request with old remember cookie → not authenticated. |
| 128 | + 3. Verify backward compatibility: existing login tests pass unchanged. |
| 129 | + 4. Run full quality gate. |
| 130 | + 5. Update knowledge map. |
| 131 | + 6. Update roadmap. |
| 132 | +- _Commands:_ `make phpstan`, `XDEBUG_MODE=off php artisan test --no-coverage`, `vendor/bin/php-cs-fixer fix`, `npm run format`, `npm run check` |
| 133 | +- _Exit:_ All quality gates pass, feature complete. |
| 134 | +- _Refs:_ All scenarios |
| 135 | + |
| 136 | +## Scenario Tracking |
| 137 | + |
| 138 | +| Scenario ID | Increment / Task reference | Notes | |
| 139 | +|-------------|---------------------------|-------| |
| 140 | +| S-023-01 | I1 | Login without remember → session only | |
| 141 | +| S-023-02 | I1 | Login with remember → remember cookie set | |
| 142 | +| S-023-03 | I1 / I4 | Session expires, remember cookie re-authenticates | |
| 143 | +| S-023-04 | I1 / I4 | Logout invalidates remember cookie | |
| 144 | +| S-023-05 | I1 | Absent field → backward compatible | |
| 145 | +| S-023-06 | I1 | Invalid credentials + remember → no cookie | |
| 146 | +| S-023-07 | I1 | LDAP + remember | |
| 147 | +| S-023-08 | I1 | LDAP unreachable + remember fallback | |
| 148 | +| S-023-09 | I2 | Checkbox defaults to unchecked | |
| 149 | +| S-023-10 | I2 | Checkbox hidden without basic auth | |
| 150 | +| S-023-11 | I1 | Non-boolean remember → 422 | |
| 151 | + |
| 152 | +## Analysis Gate |
| 153 | + |
| 154 | +Not yet completed. Will be run after spec, plan, and tasks agree. |
| 155 | + |
| 156 | +## Exit Criteria |
| 157 | + |
| 158 | +- [ ] `LoginRequest` validates optional `remember_me` boolean (defaults to `false`) |
| 159 | +- [ ] `AuthController::login()` passes `remember` to `Auth::attempt()` and `Auth::login()` |
| 160 | +- [ ] LDAP login respects `remember` flag |
| 161 | +- [ ] Login log messages include remember flag |
| 162 | +- [ ] Frontend checkbox renders conditionally (basic auth only) |
| 163 | +- [ ] `AuthService.login()` sends `remember_me` parameter |
| 164 | +- [ ] Remember cookie set on `remember_me = true`, absent on `false` |
| 165 | +- [ ] Logout invalidates remember cookie |
| 166 | +- [ ] All existing login tests pass unchanged (backward compatibility) |
| 167 | +- [ ] Translation strings present in all 22 languages |
| 168 | +- [ ] PHPStan, php-cs-fixer, npm check/format all clean |
| 169 | +- [ ] Knowledge map and roadmap updated |
| 170 | + |
| 171 | +## Follow-ups / Backlog |
| 172 | + |
| 173 | +- **Admin-configurable remember duration via UI** — Consider adding a settings UI control for the remember cookie lifetime (currently configurable via `REMEMBER_LIFETIME` env variable, default 4 weeks). |
| 174 | +- **"Remember Me" for WebAuthn** — Investigate if WebAuthn sessions can benefit from a similar persistence mechanism. |
| 175 | +- **Session management UI** — Allow users to see and revoke active remember-me sessions (list of devices/tokens). |
| 176 | + |
| 177 | +--- |
| 178 | + |
| 179 | +*Last updated: 2026-02-28* |
0 commit comments