287 lines
11 KiB
Markdown
287 lines
11 KiB
Markdown
---
|
|
phase: 93-remember-me-login
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- database/migrations/20260410_000081_add_remember_token_to_users.sql
|
|
- src/Modules/Auth/AuthService.php
|
|
- src/Modules/Auth/AuthController.php
|
|
- src/Modules/Auth/AuthMiddleware.php
|
|
- src/Modules/Users/UserRepository.php
|
|
- resources/views/auth/login.php
|
|
- resources/scss/login.scss
|
|
- resources/lang/pl.php
|
|
autonomous: true
|
|
delegation: off
|
|
---
|
|
|
|
<objective>
|
|
## Goal
|
|
Dodanie checkboxa "Zapamiętaj mnie" na stronie logowania z persistent cookie (30 dni) oraz uruchomienie działającego komunikatu błędu logowania (zamiast zaślepki placeholder).
|
|
|
|
## Purpose
|
|
Użytkownicy muszą logować się przy każdej sesji przeglądarki. "Zapamiętaj mnie" pozwala na trwałe logowanie na danym urządzeniu przez 30 dni. Jednocześnie placeholder błędu logowania staje się funkcjonalny — wyświetla rzeczywiste komunikaty.
|
|
|
|
## Output
|
|
- Migracja: kolumna `remember_token` w tabeli `users`
|
|
- Backend: generowanie/walidacja tokena, cookie, auto-login z middleware
|
|
- Frontend: checkbox w formularzu + usunięcie zaślepki błędu
|
|
</objective>
|
|
|
|
<context>
|
|
## Project Context
|
|
@.paul/PROJECT.md
|
|
@.paul/ROADMAP.md
|
|
@.paul/STATE.md
|
|
|
|
## Source Files
|
|
@src/Modules/Auth/AuthService.php
|
|
@src/Modules/Auth/AuthController.php
|
|
@src/Modules/Auth/AuthMiddleware.php
|
|
@src/Modules/Users/UserRepository.php
|
|
@resources/views/auth/login.php
|
|
@resources/scss/login.scss
|
|
@resources/lang/pl.php
|
|
@database/migrations/20260221_000001_create_users_table.sql
|
|
</context>
|
|
|
|
<acceptance_criteria>
|
|
|
|
## AC-1: Checkbox "Zapamiętaj mnie" widoczny na formularzu logowania
|
|
```gherkin
|
|
Given strona logowania /login jest wyświetlona
|
|
When użytkownik widzi formularz logowania
|
|
Then pomiędzy polem hasła a przyciskiem "Zaloguj" widoczny jest checkbox "Zapamiętaj mnie"
|
|
```
|
|
|
|
## AC-2: Persistent login przez 30 dni po zaznaczeniu checkboxa
|
|
```gherkin
|
|
Given użytkownik zaznaczył checkbox "Zapamiętaj mnie"
|
|
When loguje się poprawnymi danymi
|
|
Then przeglądarka otrzymuje cookie `remember_token` z max-age 30 dni (httponly, secure, samesite=lax)
|
|
And token jest zapisany w bazie danych (users.remember_token jako hash)
|
|
And po zamknięciu i otwarciu przeglądarki użytkownik jest nadal zalogowany
|
|
```
|
|
|
|
## AC-3: Brak persistent login bez zaznaczenia checkboxa
|
|
```gherkin
|
|
Given użytkownik NIE zaznaczył checkboxa "Zapamiętaj mnie"
|
|
When loguje się poprawnymi danymi
|
|
Then cookie `remember_token` NIE jest ustawiane
|
|
And sesja wygasa po zamknięciu przeglądarki (standardowe zachowanie)
|
|
```
|
|
|
|
## AC-4: Komunikat błędu logowania działa prawidłowo
|
|
```gherkin
|
|
Given strona logowania /login jest wyświetlona
|
|
When nie ma błędu logowania
|
|
Then placeholder błędu jest ukryty (display:none, nie opacity)
|
|
When użytkownik podaje złe dane i submittuje formularz
|
|
Then wyświetla się rzeczywisty komunikat błędu (np. "Nieprawidłowy email lub hasło")
|
|
And placeholder nie jest widoczny
|
|
```
|
|
|
|
## AC-5: Wylogowanie czyści remember token
|
|
```gherkin
|
|
Given użytkownik jest zalogowany z "Zapamiętaj mnie"
|
|
When klika "Wyloguj"
|
|
Then cookie `remember_token` jest usuwane
|
|
And token w bazie danych jest kasowany (NULL)
|
|
And użytkownik musi zalogować się ponownie
|
|
```
|
|
|
|
## AC-6: Wielourządzeniowe logowanie działa niezależnie
|
|
```gherkin
|
|
Given użytkownik zalogował się z "Zapamiętaj mnie" na urządzeniu A
|
|
When loguje się z "Zapamiętaj mnie" na urządzeniu B
|
|
Then oba urządzenia mają niezależne tokeny
|
|
And wylogowanie na urządzeniu A nie wylogowuje z urządzenia B
|
|
```
|
|
|
|
</acceptance_criteria>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Migracja DB + UserRepository + remember token backend</name>
|
|
<files>
|
|
database/migrations/20260410_000081_add_remember_token_to_users.sql,
|
|
src/Modules/Users/UserRepository.php
|
|
</files>
|
|
<action>
|
|
1. Utworzyć migrację dodającą kolumnę `remember_token VARCHAR(255) NULL` do tabeli `users`.
|
|
- Kolumna przechowuje HASH tokena (nie plaintext) — `hash('sha256', $token)`
|
|
- NULL = brak aktywnego remember me
|
|
|
|
2. W `UserRepository` dodać metody:
|
|
- `updateRememberToken(int $userId, ?string $tokenHash): void` — UPDATE users SET remember_token = :token WHERE id = :id
|
|
- `findByRememberToken(string $tokenHash): ?array` — SELECT id, name, email FROM users WHERE remember_token = :token LIMIT 1
|
|
|
|
Avoid: NIE przechowywać plaintext tokena w DB — zawsze hash('sha256', $token)
|
|
</action>
|
|
<verify>Migracja wykonuje się bez błędów; metody repozytorium istnieją i mają prepared statements</verify>
|
|
<done>AC-2 (baza), AC-6 (wielotoken) — infrastruktura DB gotowa</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: AuthService + AuthController + AuthMiddleware — logika remember me + error fix</name>
|
|
<files>
|
|
src/Modules/Auth/AuthService.php,
|
|
src/Modules/Auth/AuthController.php,
|
|
src/Modules/Auth/AuthMiddleware.php
|
|
</files>
|
|
<action>
|
|
**AuthService:**
|
|
1. Dodać stałą `REMEMBER_COOKIE = 'remember_token'` i `REMEMBER_DAYS = 30`
|
|
2. Metoda `createRememberToken(int $userId): string`:
|
|
- Generuje losowy token: `bin2hex(random_bytes(32))`
|
|
- Zapisuje hash w DB: `$this->users->updateRememberToken($userId, hash('sha256', $token))`
|
|
- Ustawia cookie: `setcookie('remember_token', $token, [opcje 30 dni, httponly, secure, samesite=lax, path=/])`
|
|
- Zwraca token (do ewentualnego użycia)
|
|
3. Metoda `loginFromRememberToken(): bool`:
|
|
- Odczytuje `$_COOKIE['remember_token']`
|
|
- Jeśli brak — return false
|
|
- Hashuje: `hash('sha256', $cookieToken)`
|
|
- Szuka usera: `$this->users->findByRememberToken($hash)`
|
|
- Jeśli znaleziony — regeneruje sesję, ustawia $_SESSION['auth_user'], return true
|
|
- Jeśli nie — usuwa cookie, return false
|
|
4. Metoda `clearRememberToken(int $userId): void`:
|
|
- `$this->users->updateRememberToken($userId, null)`
|
|
- Usuwa cookie (setcookie z max-age 0)
|
|
5. W istniejącej `logout()`: wywołać `clearRememberToken` dla aktualnego usera przed unset sesji
|
|
|
|
**AuthController:**
|
|
1. W `login()`: po udanym `$this->auth->attempt()`:
|
|
- Sprawdzić `$request->input('remember')`
|
|
- Jeśli truthy → `$this->auth->createRememberToken($userId)`
|
|
- Pobrać userId z `$this->auth->user()['id']` po attempt
|
|
2. Przekazywać `remember` checkbox state z powrotem do formularza w razie błędu (Flash::set('old_remember'))
|
|
3. W `showLogin()`: przekazać `oldRemember` z Flash do widoku
|
|
|
|
**AuthMiddleware:**
|
|
1. W `__invoke()`: jeśli `$this->auth->check()` zwraca false:
|
|
- Przed redirect na /login, spróbować `$this->auth->loginFromRememberToken()`
|
|
- Jeśli sukces → kontynuować normalnie ($next)
|
|
- Jeśli porażka → redirect /login jak dotychczas
|
|
|
|
Avoid:
|
|
- NIE przechowywać plaintext tokena w DB
|
|
- NIE ustawiać cookie bez httponly i samesite
|
|
- W logout() NAJPIERW pobrać user ID, POTEM czyścić sesję
|
|
</action>
|
|
<verify>
|
|
1. Login z remember=on → cookie `remember_token` w przeglądarce (30 dni)
|
|
2. Login bez remember → brak cookie
|
|
3. Po zamknięciu przeglądarki i otwarciu → auto-login z cookie
|
|
4. Logout → cookie usunięte + token NULL w DB
|
|
5. Błąd logowania → wyświetla komunikat (nie placeholder)
|
|
</verify>
|
|
<done>AC-2, AC-3, AC-5, AC-6 satisfied</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Frontend — checkbox, error placeholder fix, style, tłumaczenia</name>
|
|
<files>
|
|
resources/views/auth/login.php,
|
|
resources/scss/login.scss,
|
|
resources/lang/pl.php
|
|
</files>
|
|
<action>
|
|
**login.php:**
|
|
1. Usunąć blok placeholder błędu (linie 13-16: `<?php else: ?>...<?php endif; ?>`).
|
|
Zamienić na: jeśli `$errorMessage` nie pusty → wyświetl alert; w przeciwnym razie — NIC (brak zaślepki).
|
|
2. Dodać checkbox "Zapamiętaj mnie" między polem hasła a przyciskiem submit:
|
|
```php
|
|
<label class="form-field form-field--inline remember-field">
|
|
<input type="checkbox" name="remember" value="1" <?= !empty($oldRemember) ? 'checked' : '' ?>>
|
|
<span class="field-label"><?= $e($t('auth.login.remember_me')) ?></span>
|
|
</label>
|
|
```
|
|
|
|
**login.scss:**
|
|
1. Usunąć regułę `.login-alert-placeholder` (opacity: 0.56 — już niepotrzebna)
|
|
2. Dodać style dla `.remember-field`:
|
|
```scss
|
|
.remember-field {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
|
|
input[type="checkbox"] {
|
|
width: 16px;
|
|
height: 16px;
|
|
accent-color: var(--c-primary, #4f6ef7);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.field-label {
|
|
font-weight: 400;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
}
|
|
```
|
|
|
|
**pl.php:**
|
|
1. Dodać klucz `'remember_me' => 'Zapamiętaj mnie'` w sekcji `auth.login`
|
|
2. Usunąć lub zostawić klucz `error_placeholder` (nie jest już używany w widoku)
|
|
|
|
Po zmianach SCSS: zbudować CSS komendą projektu (jeśli build pipeline istnieje) lub skopiować do public/assets/css/
|
|
|
|
Avoid: NIE dodawać nowych natywnych alert()/confirm() — formularz działa przez POST redirect
|
|
</action>
|
|
<verify>
|
|
1. Strona /login wyświetla checkbox "Zapamiętaj mnie"
|
|
2. Bez błędu — brak żadnego komunikatu (nie ma zaślepki)
|
|
3. Po błędnym logowaniu — wyświetla się czerwony alert z treścią błędu
|
|
4. Checkbox zachowuje stan po błędnym logowaniu (old_remember)
|
|
</verify>
|
|
<done>AC-1, AC-4 satisfied</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<boundaries>
|
|
|
|
## DO NOT CHANGE
|
|
- src/Core/Support/Session.php (zarządzanie sesją — stabilne)
|
|
- src/Core/Security/Csrf.php (token CSRF — stabilne)
|
|
- resources/views/layouts/auth.php (layout auth — bez zmian)
|
|
- Inne moduły (Orders, Settings, Accounting itp.)
|
|
|
|
## SCOPE LIMITS
|
|
- Nie implementujemy resetowania hasła
|
|
- Nie implementujemy "wyloguj ze wszystkich urządzeń" (poza scope)
|
|
- Nie zmieniamy struktury sesji (SESSION_USER_KEY format)
|
|
- Nie dodajemy nowych zależności npm/composer
|
|
|
|
</boundaries>
|
|
|
|
<verification>
|
|
Before declaring plan complete:
|
|
- [ ] Migracja dodaje kolumnę `remember_token` do `users`
|
|
- [ ] Login z checkbox → cookie 30-dniowe + hash w DB
|
|
- [ ] Login bez checkbox → brak cookie
|
|
- [ ] Zamknięcie/otwarcie przeglądarki → auto-login działa
|
|
- [ ] Logout → cookie usunięte + DB token NULL
|
|
- [ ] Błąd logowania → widoczny komunikat
|
|
- [ ] Brak błędu → brak zaślepki/placeholdera
|
|
- [ ] SCSS zbudowane do CSS
|
|
- [ ] Tłumaczenie pl.php zaktualizowane
|
|
- [ ] All acceptance criteria met
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- All tasks completed
|
|
- All verification checks pass
|
|
- No errors or warnings introduced
|
|
- Użytkownik może logować się z persistent cookie na wielu urządzeniach niezależnie
|
|
- Komunikaty błędów logowania działają poprawnie
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.paul/phases/93-remember-me-login/93-01-SUMMARY.md`
|
|
</output>
|