---
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>
