Przejdź do treści

Użytkownicy (User)

Ostatnia aktualizacja dokumentacji: 2026-02-26 Stan synchronizacji z kodem: Zsynchronizowany

1. Opis ogólny

Domena User zarządza kontami pracowników systemu zajmij.to — administratorów, supervisorów i operatorów (employees) działających w imieniu jednostek samorządu terytorialnego (JST). Odpowiada za pełny cykl życia konta: tworzenie użytkownika z przypisaniem roli, uwierzytelnianie JWT (z opcjonalnym jednorazowym kodem OTP), zarządzanie sesjami na wielu urządzeniach poprzez mechanizm refresh tokenów oraz bezpieczeństwo konta (wykrywanie podejrzanych logowań, alerty e-mail, rate limiting). Domena nie obsługuje klientów-mieszkańców — ci należą do domeny Client.


2. Architektura domeny

2.1 Modele danych

User (Domain\User\Model\User)

Główny model reprezentujący konto pracownika systemu. Implementuje JWTSubject (uwierzytelnianie JWT), używa HasUuids (UUID jako klucz główny), HasRoles (Spatie Permission), LogsActivity (Spatie ActivityLog) oraz ReceivesWelcomeNotification (Spatie Welcome Notification).

Pole Typ (PHP) Cast Opis
id string string UUID, klucz główny
name string string Imię i nazwisko, min. 5 znaków
email string string Adres e-mail, unikalny, służy do logowania
email_verified_at Carbon\|null datetime:Y-m-d H:i:s Data weryfikacji e-mail
welcome_valid_until Carbon\|null datetime:Y-m-d H:i:s Data ważności linku powitalnego
password string string Hasło hashowane bcrypt (ukryte w serializacji)
remember_token string\|null string Token "zapamiętaj mnie" (ukryty w serializacji)
is_active bool boolean Czy konto jest aktywne
gender string\|null string Płeć: male, female, none
phone string\|null string Numer telefonu, tylko cyfry, 7–15 znaków
rodo_agreement bool boolean Zgoda RODO
statute_agreement bool boolean Zgoda na regulamin systemu
login_with_one_time_password bool boolean Czy wymagane logowanie przez OTP
created_at Carbon datetime:Y-m-d H:i:s Data utworzenia
updated_at Carbon datetime:Y-m-d H:i:s Data ostatniej modyfikacji

Relacje:

Metoda Typ Opis
role() HasOneThrough Jedna rola przez ModelHasRoles (Spatie)
userOneTimePassword() HasOne Aktywny jednorazowy kod OTP
organizationEmployees() HasMany Przypisania do organizacji
complaintMessages() HasMany Wiadomości w reklamacjach (jako autor)

Metody cache:

  • organizationIds(): array — zwraca ID organizacji użytkownika (cache array-store, rememberForever)
  • managedItemIds(): array — zwraca ID obiektów zarządzanych przez użytkownika (cache array-store, rememberForever)

Observer: UserObserver::class (via atrybut #[ObservedBy])


UserOneTimePassword (Domain\User\Model\UserOneTimePassword)

Przechowuje aktywny jednorazowy kod logowania (OTP) dla użytkownika. Każdy użytkownik może mieć co najwyżej jeden aktywny OTP (poprzedni jest usuwany przed wygenerowaniem nowego).

Pole Typ (PHP) Opis
id string UUID, klucz główny
user_id string FK → users.id (cascade delete)
password string Kod OTP hashowany bcrypt (ukryty w serializacji)
expired_at Carbon\|null Data wygaśnięcia kodu (TTL = 5 minut)
created_at Carbon Data utworzenia
updated_at Carbon Data modyfikacji

Relacje: user(): BelongsToUser

Observer: UserOneTimePasswordObserver::class


UserRefreshToken (Domain\User\Model\UserRefreshToken)

Przechowuje refresh tokeny (JWT) powiązane z konkretnymi urządzeniami/sesjami użytkownika. Token nie jest przechowywany wprost — zapisywany jest jedynie jego hash SHA-256.

Pole Typ (PHP) Opis
id string UUID, klucz główny
user_id string FK → users.id (cascade delete)
token_hash string SHA-256 hash refresh tokenu (unikalna, max 64 znaki)
device string\|null User-Agent przeglądarki/aplikacji (max 255 znaków)
ip_address string\|null Adres IP podczas tworzenia tokenu
fingerprint string\|null SHA-256 hash User-Agent (powiązanie z urządzeniem)
expires_at Carbon\|null Data wygaśnięcia tokenu
revoked_at Carbon\|null Data unieważnienia tokenu (NULL = aktywny)
created_at Carbon Data utworzenia
updated_at Carbon Data modyfikacji

Relacje: user(): BelongsToUser

Indeksy bazy danych: (user_id, revoked_at), expires_at, token_hash


2.2 Diagram relacji

erDiagram
    users {
        uuid id PK
        string name
        string email
        boolean is_active
        enum gender
        string phone
        boolean rodo_agreement
        boolean statute_agreement
        boolean login_with_one_time_password
        timestamp welcome_valid_until
        timestamp email_verified_at
    }

    user_one_time_passwords {
        uuid id PK
        uuid user_id FK
        string password
        datetime expired_at
    }

    user_refresh_tokens {
        uuid id PK
        uuid user_id FK
        string token_hash
        string device
        string ip_address
        string fingerprint
        datetime expires_at
        datetime revoked_at
    }

    password_reset_tokens {
        string email PK
        string token
        timestamp created_at
    }

    model_has_roles {
        uuid role_id FK
        string model_type
        uuid model_id FK
    }

    roles {
        uuid id PK
        string name
    }

    organization_employees {
        uuid user_id FK
        uuid organization_id FK
    }

    users ||--o| user_one_time_passwords : "hasOne"
    users ||--o{ user_refresh_tokens : "hasMany"
    users ||--o{ model_has_roles : "hasMany"
    model_has_roles }o--|| roles : "belongsTo"
    users ||--o{ organization_employees : "hasMany"

2.3 Struktura tabel w bazie danych

Tabela users

Nazwa kolumny Typ Nullable Default Opis
id uuid Nie Klucz główny (UUID)
name varchar Nie Imię i nazwisko
email varchar (unique) Nie Adres e-mail (login)
email_verified_at timestamp Tak NULL Weryfikacja e-mail
welcome_valid_until timestamp Tak NULL Ważność linku powitalnego
password varchar Nie Hasło (bcrypt)
remember_token varchar(100) Tak NULL Token sesji webowej
is_active boolean Nie true Aktywność konta
gender enum(male,female,none) Nie Płeć
phone varchar Nie Numer telefonu
rodo_agreement boolean Nie false Zgoda RODO
statute_agreement boolean Nie false Zgoda na regulamin
login_with_one_time_password boolean Nie false Wymaganie OTP przy logowaniu
created_at timestamp Tak NULL Data utworzenia
updated_at timestamp Tak NULL Data modyfikacji

Tabela user_one_time_passwords

Nazwa kolumny Typ Nullable Default Opis
id uuid Nie Klucz główny (UUID)
user_id uuid (FK) Nie Powiązanie z users.id (cascade delete)
password varchar Nie Kod OTP (bcrypt hash)
expired_at datetime Nie Data wygaśnięcia (TTL 5 minut)
created_at timestamp Tak NULL Data utworzenia
updated_at timestamp Tak NULL Data modyfikacji

Tabela user_refresh_tokens

Nazwa kolumny Typ Nullable Default Opis
id uuid Nie Klucz główny (UUID)
user_id uuid (FK) Nie Powiązanie z users.id (cascade delete)
token_hash varchar(64) (unique) Nie SHA-256 hash tokenu
device varchar Tak NULL User-Agent (max 255 znaków)
ip_address varchar Tak NULL Adres IP
fingerprint varchar(64) Tak NULL SHA-256 hash User-Agent (dodany migracją 2026-02-13)
expires_at datetime Nie Data wygaśnięcia
revoked_at datetime Tak NULL Data unieważnienia (NULL = aktywny)
created_at timestamp Tak NULL Data utworzenia
updated_at timestamp Tak NULL Data modyfikacji

Tabela password_reset_tokens

Nazwa kolumny Typ Nullable Opis
email varchar (PK) Nie E-mail użytkownika
token varchar Nie Token resetu hasła
created_at timestamp Tak Data wygenerowania

Tabela sessions

Nazwa kolumny Typ Nullable Opis
id varchar (PK) Nie ID sesji
user_id uuid (FK) Tak Powiązanie z users.id (cascade delete)
ip_address varchar(45) Tak Adres IP
user_agent text Tak User-Agent
payload longtext Nie Payload sesji
last_activity integer (indexed) Nie Ostatnia aktywność (timestamp Unix)

3. Endpointy API

Wszystkie endpointy przechodzą przez globalny middleware (require_app_client, global_database_transaction, app_mode, check_site_enable, set_locale, security_headers).

3.1 Endpointy publiczne (no-auth, prefix: /api/no-auth)


POST /api/no-auth/login

  • Opis: Logowanie użytkownika. Zwraca JWT access token i ustawia HttpOnly cookie z refresh tokenem.
  • Autoryzacja: Publiczny (throttle: login)
  • Body (Request):
{
  "email": "jan.kowalski@example.com",
  "password": "TajneHaslo123!",
  "one_time_password": "482391"
}

Walidacja (LoginUserAuthControllerRequest):

Pole Reguły Opis
email required, string, email Adres e-mail
password required, string Hasło
one_time_password sometimes, nullable, string Kod OTP (wymagany jeśli konto ma OTP włączone)
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": {
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_name": "Jan Kowalski",
    "user_role": "admin",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "access_token_expires_at": "2026-02-26 15:30:00",
    "message": "You have been logged in successfully",
    "needed_one_time_password": false
  }
}

Cookie: refresh_token (HttpOnly, Secure, SameSite=Strict, path=/api, TTL wg jwt.refresh_cookie_minutes — domyślnie 10080 minut = 7 dni)

  • Response (błędy):
Kod HTTP Sytuacja
422 Nieprawidłowe dane (email/hasło, konto nieaktywne, błędny OTP)
429 Zbyt wiele prób logowania (max 5 prób, rate limited)
  • Reguły biznesowe:
    1. Użytkownik musi istnieć w bazie (users.email)
    2. Konto musi być aktywne (is_active = true)
    3. Hasło musi być prawidłowe (bcrypt Hash::check)
    4. Jeśli login_with_one_time_password = true: wymagane podanie kodu OTP
    5. Jeśli OTP wygasł lub nie istnieje — nowy OTP jest automatycznie wysyłany na e-mail
    6. Logowanie z nowego urządzenia lub IP (sprawdzanie ostatnich 30 dni) wyzwala powiadomienie e-mail UserNewLoginNotification
    7. Rate limiting: 5 prób na klucz {email}|{ip}, blokada z informacją o czasie oczekiwania

POST /api/no-auth/refresh-token

  • Opis: Odświeżenie access tokenu przy użyciu refresh tokenu z cookie. Implementuje rotację tokenów — stary token jest unieważniany, nowy jest wystawiany.
  • Autoryzacja: Publiczny (throttle: refresh-token). Refresh token pobierany z cookie refresh_token.
  • Body (Request): Brak (token pobierany z HttpOnly cookie)
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": {
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
    "access_token_expires_at": "2026-02-26 15:45:00",
    "message": "Token refreshed successfully"
  }
}

Cookie: Nowy refresh_token (HttpOnly, Secure, SameSite=Strict)

  • Response (błędy):
Kod HTTP Sytuacja
401 Brak cookie, token nieważny lub wygasły
500 Błąd rotacji tokenu (RefreshTokenRotationException)
  • Reguły biznesowe:
    1. Token musi istnieć w tabeli user_refresh_tokens (wyszukiwanie po token_hash = SHA256(token))
    2. Token musi być aktywny (revoked_at IS NULL) i nie wygasły (expires_at > now())
    3. Jeśli token ma fingerprint: weryfikowany SHA-256 hash User-Agent; niezgodność = unieważnienie wszystkich tokenów + alert e-mail UserSecurityAlertNotification z typem fingerprint_mismatch
    4. Próba użycia już unieważnionego tokenu = unieważnienie WSZYSTKICH tokenów użytkownika + alert e-mail z typem token_reuse
    5. Po walidacji: stary token otrzymuje revoked_at = now(), nowy token jest tworzony

POST /api/no-auth/reset-password

  • Opis: Ustawia nowe hasło przy użyciu tokenu z linku resetującego.
  • Autoryzacja: Publiczny (throttle: public-store)
  • Body (Request):
{
  "token": "abc123def456...",
  "email": "jan.kowalski@example.com",
  "password": "NoweHaslo123!",
  "password_confirmation": "NoweHaslo123!"
}

Walidacja (NewPasswordUserAuthControllerRequest):

Pole Reguły
token required
email required, email
password required, confirmed, Password::defaults()
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "Password has been successfully changed"
}
  • Response (błędy):
Kod HTTP Sytuacja
422 Nieprawidłowy token, e-mail lub niezgodne hasła
  • Reguły biznesowe:
    1. Używa Laravel Password Broker (Password::reset)
    2. Po zresetowaniu hasła: ustawia nowy password (bcrypt) i generuje nowy remember_token
    3. Emituje event PasswordReset

POST /api/no-auth/store-user-one-time-password

  • Opis: Wysyła jednorazowy kod logowania (OTP) na e-mail użytkownika. Weryfikuje tożsamość przez parę e-mail + telefon.
  • Autoryzacja: Publiczny (throttle: public-store)
  • Body (Request):
{
  "email": "jan.kowalski@example.com",
  "phone": "512345678"
}

Walidacja (StoreUserOneTimePasswordControllerRequest):

Pole Reguły
email required, email, exists:users,email
phone required, exists:users,phone
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "A one-time login code has been sent to you"
}
  • Response (konflikt, HTTP 409):
{
  "is_error": false,
  "status_code": 409,
  "data": "You must wait 243 seconds before you can resend the one-time login code."
}
  • Response (błędy):
Kod HTTP Sytuacja
404 Nie znaleziono użytkownika z podaną parą email+phone
409 Aktualny OTP jeszcze nie wygasł — zwraca czas oczekiwania w sekundach
422 Błąd walidacji
  • Reguły biznesowe:
    1. Użytkownik musi istnieć i mieć login_with_one_time_password = true (sprawdzane w SendUserOneTimePassword)
    2. Jeśli aktywny OTP jeszcze nie wygasł — ponowne wysłanie jest zablokowane; zwracana jest liczba sekund do wygaśnięcia
    3. Kod OTP: losowa liczba całkowita z zakresu 111111–999999 (6 cyfr)
    4. TTL kodu OTP: 5 minut (wartość UserEnumService::USER_OTP_TTL_MINUTES = '5')
    5. Kod jest hashowany bcrypt przed zapisem w bazie
    6. Kanał dostarczenia: wyłącznie e-mail (UserEnumService::ONE_TIME_PASSWORD_CHANNEL_MAIL = 'mail')

3.2 Endpointy uwierzytelnione (auth, middleware: auth:api, user_is_active, to_user_access, user_accepted_agreements)


GET /api/me

  • Opis: Zwraca dane zalogowanego użytkownika.
  • Autoryzacja: Role: admin, supervisor, employee
  • Body (Request): Brak
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Jan Kowalski",
    "email": "jan.kowalski@example.com",
    "gender": "male",
    "gender_lang": "Man",
    "phone": "512345678",
    "is_active": true,
    "rodo_agreement": true,
    "statute_agreement": true,
    "login_with_one_time_password": false,
    "created_at": "2026-01-15 10:00:00",
    "updated_at": "2026-02-20 14:30:00",
    "role": {
      "id": "role-uuid",
      "name": "admin"
    }
  }
}

POST /api/logout

  • Opis: Wylogowuje użytkownika z bieżącej sesji — unieważnia JWT access token i refresh token z cookie.
  • Autoryzacja: Role: admin, supervisor, employee
  • Body (Request): Brak (refresh token pobierany z cookie)
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "You have been logged out successfully"
}

Cookie: refresh_token usuwany (forget)


POST /api/logout-everywhere

  • Opis: Wylogowuje użytkownika ze wszystkich urządzeń — unieważnia JWT access token i WSZYSTKIE aktywne refresh tokeny.
  • Autoryzacja: Role: admin, supervisor, employee
  • Body (Request): Brak
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "You have been logged out from all devices"
}

Cookie: refresh_token usuwany (forget)


POST /api/forgot-password

  • Opis: Wysyła link do resetowania hasła na podany adres e-mail.
  • Autoryzacja: Role: admin, supervisor
  • Body (Request):
{
  "email": "jan.kowalski@example.com"
}

Walidacja (inline w kontrolerze): emailrequired, email

  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "Password reset link has been sent successfully"
}
  • Response (błędy):
Kod HTTP Sytuacja
422 Nie można wysłać linku (np. e-mail nie istnieje w systemie)

GET /api/users

  • Opis: Lista użytkowników z filtrowaniem i paginacją.
  • Autoryzacja: Role: admin, supervisor, employee
  • Query Parameters:
Parametr Opis
filter[id] Filtrowanie po ID (exact)
filter[role.name] Filtrowanie po nazwie roli (exact): admin, supervisor, employee
filter[organizationEmployees.organization_id] Filtrowanie po ID organizacji
page Numer strony (paginacja)
per_page Liczba wyników na stronę
sort Sortowanie (Spatie QueryBuilder)
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Jan Kowalski",
      "email": "jan.kowalski@example.com",
      "gender": "male",
      "gender_lang": "Man",
      "phone": "512345678",
      "is_active": true,
      "rodo_agreement": true,
      "statute_agreement": true,
      "login_with_one_time_password": false,
      "created_at": "2026-01-15 10:00:00",
      "updated_at": "2026-02-20 14:30:00",
      "role": { "id": "role-uuid", "name": "admin" }
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 15,
    "total": 42
  }
}

GET /api/users/{userId}

  • Opis: Szczegóły konkretnego użytkownika.
  • Autoryzacja: Role: admin, supervisor, employee
  • Parametry URL: userId — UUID użytkownika
  • Response (sukces, HTTP 200): Jak wyżej (pojedynczy obiekt data)
  • Response (błędy):
Kod HTTP Sytuacja
404 Użytkownik nie istnieje

POST /api/users

  • Opis: Tworzy nowe konto użytkownika. Administrator musi podać rolę. Jeśli hasło nie jest podane, generowane jest losowe. Po utworzeniu wysyłana jest wiadomość powitalna oraz link do ustawienia hasła.
  • Autoryzacja: Tylko rola: admin
  • Body (Request):
{
  "name": "Anna Nowak",
  "email": "anna.nowak@example.com",
  "password": "HasloOpcjonalne1!",
  "password_confirmation": "HasloOpcjonalne1!",
  "gender": "female",
  "phone": "600123456",
  "rodo_agreement": true,
  "statute_agreement": true,
  "login_with_one_time_password": false,
  "role_id": "role-uuid-supervisor"
}

Walidacja (StoreUserControllerRequest):

Pole Reguły
name required, string, min:5
email required, email, unique:users,email
password nullable, sometimes, confirmed, Password::min(8)
gender required, in:male,female,none
phone required, string, min:7, max:15, regex:/^[0-9]+$/
rodo_agreement required, boolean
statute_agreement required, boolean
login_with_one_time_password required, boolean
role_id required, uuid, exists:roles,id
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "User has been successfully added"
}
  • Reguły biznesowe:
    1. Jeśli password nie podano — generowane jest losowe 8-znakowe hasło (bcrypt)
    2. Rola jest przypisywana przez Spatie assignRole
    3. Link powitalny ważny 3 dni (now()->addDays(3)) — wysyłany przez sendWelcomeNotification
    4. Observer UserObserver::created wyzwala Password::sendResetLink — wysyła link do ustawienia hasła
    5. Notification UserWelcomeNotification informuje o utworzeniu konta

PATCH /api/users/{userId}

  • Opis: Aktualizuje dane użytkownika.
  • Autoryzacja: Role: admin, supervisor, employee
  • Parametry URL: userId — UUID użytkownika
  • Body (Request):
{
  "name": "Anna Nowak-Kowalska",
  "email": "anna.nowak@example.com",
  "password": "NoweHaslo123!",
  "password_confirmation": "NoweHaslo123!",
  "gender": "female",
  "phone": "600123456",
  "rodo_agreement": true,
  "statute_agreement": true,
  "login_with_one_time_password": true
}

Walidacja (UpdateUserControllerRequest):

Pole Reguły
name required, string, min:5
email sometimes, nullable, email, unique:users,email,{userId}
password sometimes, confirmed, Password::min(8)
gender required, in:male,female,none
phone required, string, min:7, max:15, regex:/^[0-9]+$/
rodo_agreement required, boolean
statute_agreement required, boolean
login_with_one_time_password required, boolean
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "User has been successfully updated"
}
  • Reguły biznesowe:
    1. Jeśli password podano — jest hashowane bcrypt przed zapisem
    2. Observer UserObserver::updated obsługuje zmianę is_active: jeśli konto zostało dezaktywowane (is_active = false), wszystkie refresh tokeny są natychmiast unieważniane

PATCH /api/users/{userId}/change-state

  • Opis: Przełącza stan aktywności konta użytkownika (is_active).
  • Autoryzacja: Tylko rola: admin
  • Parametry URL: userId — UUID użytkownika
  • Body (Request): Brak
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "..."
}
  • Response (błędy):
Kod HTTP Sytuacja
409 Próba zmiany własnego konta (ChangeStateUserException)
404 Użytkownik nie istnieje
  • Reguły biznesowe:
    1. Administrator nie może zmienić stanu swojego własnego konta
    2. Dezaktywacja konta (is_active = false) powoduje unieważnienie wszystkich refresh tokenów (via UpdatedUserObserver)

PATCH /api/users/{userId}/roles

  • Opis: Przypisuje rolę użytkownikowi (zastępuje istniejącą).
  • Autoryzacja: Tylko rola: admin
  • Parametry URL: userId — UUID użytkownika
  • Body (Request):
{
  "role_id": "role-uuid-supervisor"
}

Walidacja (AssignRoleUserRoleControllerRequest):

Pole Reguły
role_id required, uuid, exists:roles,id
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "The role was successfully assigned"
}
  • Response (błędy):
Kod HTTP Sytuacja
409 Próba zmiany roli własnego konta (ChangeUserRoleException)
404 Użytkownik nie istnieje
  • Reguły biznesowe:
    1. Stare role są usuwane przez syncRoles([]) przed przypisaniem nowej
    2. Zmiana jest logowana do ActivityLog (stara rola vs. nowa rola)
    3. Administrator nie może zmienić własnej roli

DELETE /api/users/{userId}/roles

  • Opis: Usuwa rolę z użytkownika.
  • Autoryzacja: Tylko rola: admin
  • Parametry URL: userId — UUID użytkownika
  • Body (Request): Brak
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "Role was successfully removed from user"
}
  • Response (błędy):
Kod HTTP Sytuacja
409 Próba usunięcia roli z własnego konta
404 Użytkownik nie istnieje

GET /api/user-refresh-tokens

  • Opis: Lista refresh tokenów bieżącego użytkownika (wszystkich urządzeń). Pole is_current wskazuje, który token odpowiada bieżącej sesji.
  • Autoryzacja: Role: admin, supervisor, employee
  • Query Parameters:
Parametr Opis
filter[id] Filtrowanie po ID (exact)
filter[user_id] Filtrowanie po user_id (exact)
filter[device] Filtrowanie po urządzeniu (exact)
filter[ip_address] Filtrowanie po adresie IP (exact)
page Paginacja
per_page Wyniki na stronę
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": [
    {
      "id": "token-uuid",
      "user_id": "user-uuid",
      "device": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
      "ip_address": "192.168.1.100",
      "expires_at": "2026-03-05 10:00:00",
      "revoked_at": null,
      "created_at": "2026-02-26 10:00:00",
      "updated_at": "2026-02-26 10:00:00",
      "is_current": true
    }
  ]
}
  • Reguły biznesowe:
    1. Wyniki sortowane malejąco po created_at
    2. is_current jest obliczane dynamicznie — porównywany jest SHA-256 hash tokenu z cookie z tokenem w bazie

DELETE /api/user-refresh-tokens/{userRefreshTokenId}

  • Opis: Unieważnia konkretny refresh token (sesję).
  • Autoryzacja: Role: admin, supervisor, employee
  • Parametry URL: userRefreshTokenId — UUID tokenu
  • Body (Request): Brak
  • Response (sukces, HTTP 200):
{
  "is_error": false,
  "status_code": 200,
  "data": "User refresh token has been revoked successfully"
}
  • Response (błędy):
Kod HTTP Sytuacja
404 Token nie istnieje lub już unieważniony (revoked_at IS NOT NULL)

4. Logika biznesowa

4.1 Główne procesy

Proces tworzenia użytkownika (klasa CreateUser)

flowchart TD
    A[POST /api/users] --> B[Walidacja StoreUserControllerRequest]
    B --> C[CreateUser::__invoke]
    C --> D{Hasło podane?}
    D -->|Tak| E[bcrypt hasło]
    D -->|Nie| F[Hash::make losowe 8 znaków]
    E --> G[User::create]
    F --> G
    G --> H[Role::find + assignRole]
    H --> I[sendWelcomeNotification - ważność 3 dni]
    I --> J[UserObserver::created]
    J --> K[Password::sendResetLink]
    K --> L[Wysłanie emaila z linkiem do ustawienia hasła]

Proces logowania z OTP (klasy LoginUserAuthControllerRequest + LoginUserAuthController)

sequenceDiagram
    participant K as Klient
    participant R as LoginRequest
    participant C as LoginController
    participant E as Email

    K->>R: POST /login {email, password}
    R->>R: ensureIsNotRateLimited (max 5 prób)
    R->>R: User::where(email) — znajdź użytkownika
    R->>R: Sprawdź is_active
    R->>R: Hash::check(password)
    R->>R: Czy login_with_one_time_password?
    alt OTP wymagane
        R->>R: Czy OTP istnieje i nie wygasł?
        alt OTP brak lub wygasł
            R->>E: SendUserOneTimePassword
        end
        R->>R: Czy one_time_password podany?
        alt OTP nie podany
            R-->>K: 422 OTP required
        end
        R->>R: Hash::check(one_time_password, stored_hash) + expired_at
        alt OTP nieprawidłowy
            R-->>K: 422 Invalid OTP
        end
    end
    R->>C: authenticate() OK
    C->>C: IsNewDeviceOrIpUserRefreshToken (30 dni historii)
    alt Nowe urządzenie/IP
        C->>E: UserNewLoginNotification (async job)
    end
    C->>C: JWTAuth::fromUser → access token
    C->>C: CreateUserRefreshToken → refresh token + cookie
    C->>C: RemoveUserOneTimePassword
    C-->>K: 200 {access_token, ...} + cookie refresh_token

Proces rotacji refresh tokenu (klasa RefreshTokenUserAuthController)

flowchart TD
    A[POST /refresh-token] --> B{Cookie refresh_token?}
    B -->|Nie| C[InvalidRefreshTokenException - 401]
    B -->|Tak| D[ValidateUserRefreshToken]
    D --> E[hash SHA-256 tokenu]
    E --> F[FindByTokenHashUserRefreshToken]
    F --> G{Token znaleziony?}
    G -->|Nie| C
    G -->|Tak| H{revoked_at IS NULL AND expires_at > now?}
    H -->|Nie| I{Był unieważniony?}
    I -->|Tak| J[RevokeAllUserRefreshTokens + SecurityAlert email: token_reuse]
    I -->|Nie| C
    H -->|Tak| K{Fingerprint istnieje?}
    K -->|Tak| L{SHA-256 UserAgent match?}
    L -->|Nie| M[RevokeAll + SecurityAlert email: fingerprint_mismatch]
    L -->|Tak| N[JWTAuth payload check: refresh=true]
    K -->|Nie| N
    N --> O[RotateUserRefreshToken]
    O --> P[RevokeOneUserRefreshToken stary]
    P --> Q[CreateUserRefreshToken nowy]
    Q --> R[JWTAuth::fromUser nowy access token]
    R --> S[200 {access_token} + nowy cookie]

Proces dezaktywacji konta (observer UpdatedUserObserver)

flowchart TD
    A[PATCH /users/id/change-state] --> B{userId == Auth::id?}
    B -->|Tak| C[ChangeStateUserException - 409]
    B -->|Nie| D[changeStateXController - przełącz is_active]
    D --> E[UserObserver::updated]
    E --> F{is_active zmieniło się?}
    F -->|Tak i nowa wartość = false| G[RevokeAllUserRefreshTokens]
    F -->|Inne zmiany| H[Koniec]
    G --> H

4.2 Reguły biznesowe

  1. Każdy użytkownik ma dokładnie jedną rolę systemową (admin, supervisor, employee).
  2. Rola jest przypisywana przy tworzeniu konta — pole role_id jest wymagane.
  3. Zmiana roli zastępuje wszystkie poprzednie role użytkownika (syncRoles([])).
  4. Administrator nie może zmienić stanu aktywności ani roli własnego konta.
  5. Dezaktywacja konta natychmiast unieważnia wszystkie aktywne refresh tokeny.
  6. Kod OTP jest 6-cyfrową liczbą całkowitą (111111–999999), hashowaną bcrypt, ważną 5 minut.
  7. Jeśli aktywny OTP jeszcze nie wygasł, ponowne żądanie jest odrzucane z informacją o czasie oczekiwania.
  8. Refresh token jest przechowywany jako SHA-256 hash — nigdy w postaci jawnej.
  9. Każdy refresh token jest powiązany z urządzeniem (User-Agent) przez SHA-256 fingerprint.
  10. Próba użycia unieważnionego tokenu traktowana jest jako atak — wszystkie tokeny użytkownika są unieważniane.
  11. Niezgodność fingerprinta przy odświeżaniu tokenu traktowana jest jako kradzież tokenu — wszystkie tokeny są unieważniane.
  12. Logowanie z nowego urządzenia lub IP (w oknie 30 dni) wyzwala powiadomienie e-mail.
  13. Wygasłe OTP-y są usuwane przez komendę CRON user-one-time-password:remove-expired (codziennie 00:40).
  14. Wygasłe i unieważnione refresh tokeny (starsze niż 7 dni) są usuwane przez komendę CRON user-refresh-token:cleanup-expired (codziennie 00:50).
  15. Po przekroczeniu 5 nieudanych prób logowania (klucz {email}|{ip}) konto jest tymczasowo blokowane (rate limiting).
  16. Przy tworzeniu użytkownika: jeśli hasło nie jest podane, generowane jest losowe 8-znakowe, a link do ustawienia hasła jest wysyłany automatycznie.

4.3 Walidacje

Logowanie (LoginUserAuthControllerRequest)

Pole Reguły Uwagi
email required, string, email
password required, string
one_time_password sometimes, nullable, string Wymagany tylko gdy login_with_one_time_password = true

Tworzenie użytkownika (StoreUserControllerRequest)

Pole Reguły
name required, string, min:5
email required, email, unique:users,email
password nullable, sometimes, confirmed, Password::min(8)
gender required, in:male,female,none
phone required, string, min:7, max:15, regex:/^[0-9]+$/
rodo_agreement required, boolean
statute_agreement required, boolean
login_with_one_time_password required, boolean
role_id required, uuid, exists:roles,id

Aktualizacja użytkownika (UpdateUserControllerRequest)

Pole Reguły Różnica vs. tworzenie
name required, string, min:5
email sometimes, nullable, email, unique:users,email,{userId} Ignoruje własny email
password sometimes, confirmed, Password::min(8) Opcjonalne
gender required, in:male,female,none
phone required, string, min:7, max:15, regex:/^[0-9]+$/
rodo_agreement required, boolean
statute_agreement required, boolean
login_with_one_time_password required, boolean
role_id Brak — edycja roli przez osobny endpoint

Reset hasła (NewPasswordUserAuthControllerRequest)

Pole Reguły
token required
email required, email
password required, confirmed, Password::defaults()

Żądanie OTP (StoreUserOneTimePasswordControllerRequest)

Pole Reguły
email required, email, exists:users,email
phone required, exists:users,phone

Przypisanie roli (AssignRoleUserRoleControllerRequest)

Pole Reguły
role_id required, uuid, exists:roles,id

5. Autoryzacja i uprawnienia

Akcja Admin Supervisor Employee Warunek dodatkowy
GET /me Tak Tak Tak
POST /logout Tak Tak Tak
POST /logout-everywhere Tak Tak Tak
POST /forgot-password Tak Tak Nie
GET /users Tak Tak Tak
GET /users/{id} Tak Tak Tak
POST /users Tak Nie Nie
PATCH /users/{id} Tak Tak Tak
PATCH /users/{id}/change-state Tak Nie Nie Nie można zmienić własnego konta
PATCH /users/{id}/roles Tak Nie Nie Nie można zmienić własnej roli
DELETE /users/{id}/roles Tak Nie Nie Nie można zmienić własnej roli
GET /user-refresh-tokens Tak Tak Tak
DELETE /user-refresh-tokens/{id} Tak Tak Tak

Publiczne (no-auth):

Akcja Opis
POST /login Logowanie
POST /refresh-token Odświeżenie tokenu
POST /reset-password Ustawienie nowego hasła przez token
POST /store-user-one-time-password Wysłanie kodu OTP

6. Eventy i efekty uboczne

Zdarzenia w obserwatorach (UserObserver)

Zdarzenie modelu Klasa obsługująca Efekt
User::created CreatedUserObserver Password::sendResetLink — wysłanie e-maila z linkiem do ustawienia hasła
User::updated UpdatedUserObserver Jeśli is_active zmieniło się na falseRevokeAllUserRefreshTokens

Eventy wyzwalane w kodzie

Zdarzenie Gdzie wyzwalane Efekt
Lockout (Illuminate) LoginUserAuthControllerRequest::ensureIsNotRateLimited Rate limiting lockout
PasswordReset (Illuminate\Auth\Events) SaveResetNewUserPassword Wewnętrzny event Laravel po udanym resecie hasła

Asynchroniczne powiadomienia (via SimpleSendNotificationJob)

Sytuacja Klasa notification Kolejka
Logowanie z nowego urządzenia/IP UserNewLoginNotification notifications
Wykrycie token_reuse UserSecurityAlertNotification notifications
Wykrycie fingerprint_mismatch UserSecurityAlertNotification notifications
Wysłanie OTP UserOneTimePasswordNotification notifications

Logowanie aktywności (ActivityLog)

  • Zmiany roli użytkownika są logowane przez AssignRemoveUserRole przez createCustomActivityLog z typem UPDATED_EVENT
  • Model User implementuje LogsActivity (Spatie) z konfigurowanymi polami (przez getActivitylogOptionsConfig())

7. Notyfikacje

Notification Kiedy wysyłana Odbiorca Kanał Treść
UserWelcomeNotification Po User::create Nowy użytkownik (user->email) mail "Konto administracyjne zostało utworzone" — informuje o założeniu konta
UserResetPasswordNotification Przez Laravel Password Broker (sendResetLink) Użytkownik mail Link do resetu hasła, ważny przez czas z auth.passwords.*.expire
UserOneTimePasswordNotification Przy logowaniu OTP lub żądaniu OTP Użytkownik mail Kod 6-cyfrowy + data wygaśnięcia
UserNewLoginNotification Logowanie z nowego urządzenia lub IP (okno 30 dni) Użytkownik mail Czas logowania, IP, urządzenie (max 100 znaków)
UserSecurityAlertNotification token_reuse lub fingerprint_mismatch Użytkownik mail Alert bezpieczeństwa z IP i User-Agent; zalecenia działań

Wszystkie powiadomienia rozszerzają CustomNotification z Infrastructure\Vendor\CustomNotification. Powiadomienia UserNewLoginNotification, UserOneTimePasswordNotification i UserSecurityAlertNotification są wysyłane asynchronicznie przez SimpleSendNotificationJob.

Powiadomienie powitalne (UserWelcomeNotification) jest wysyłane synchronicznie przez User::sendWelcomeNotification z użyciem metody simpleSendNotification('mail', [$this->email], ...) z TraitSupport.


8. Powiązania z innymi domenami

Domena Klasa/Model Typ powiązania Opis
Role (Domain\Role) Role, ModelHasRoles, RoleEnumService HasOneThrough via ModelHasRoles Każdy użytkownik ma przypisaną rolę (admin, supervisor, employee)
Organization (Domain\Organization) OrganizationEmployee HasMany Użytkownik może być pracownikiem jednej lub wielu organizacji
Item (Domain\Item) ItemManager Pośrednie przez managedItemIds() Użytkownik może zarządzać obiektami (item managers)
Complaint (Domain\Complaint) ComplaintMessage HasMany Użytkownik jest autorem wiadomości w reklamacjach
ActivityLog (Domain\ActivityLog) TraitActivityLog Trait Logowanie zmian w domenie User (w tym zmiany ról)
Support (Support) TraitSupport, CanResetPassword Trait Wspólna logika kontrolerów, reset hasła
Notifiable (Infrastructure\Vendor\Notifiable) Trait Wysyłanie powiadomień

9. Konfiguracja

Parametry JWT (plik config/jwt.php, zmienne środowiskowe)

Parametr Zmienna ENV Wartość domyślna Opis
jwt.ttl JWT_TTL 15 Czas życia access tokenu (minuty)
jwt.refresh_ttl JWT_REFRESH_TTL 10080 Czas życia refresh tokenu (minuty = 7 dni)
jwt.refresh_cookie_name JWT_REFRESH_COOKIE_NAME refresh_token Nazwa HttpOnly cookie
jwt.refresh_cookie_minutes JWT_REFRESH_COOKIE_MINUTES 10080 TTL cookie refresh tokenu (minuty = 7 dni)

Parametry OTP (UserEnumService)

Stała Wartość Opis
USER_OTP_TTL_MINUTES 5 Czas ważności kodu OTP (minuty)
ONE_TIME_PASSWORD_CHANNEL_MAIL mail Kanał dostarczenia OTP

Dozwolone wartości płci (UserEnumService)

Wartość Opis
male Mężczyzna
female Kobieta
none Nie podano

Wymagane zgody (UserEnumService::getUserNecessaryAgreements)

Kolumna Opis
rodo_agreement Zgoda RODO (wymagana przez middleware user_accepted_agreements)
statute_agreement Zgoda na regulamin systemu (wymagana przez middleware user_accepted_agreements)

10. Komendy CRON

Komenda Sygnatura artisan Harmonogram Opis
CleanupExpiredUserRefreshTokensCommand user-refresh-token:cleanup-expired Codziennie 00:50 Usuwa tokeny: wygasłe (expires_at < now()) lub unieważnione starsze niż 7 dni
RemoveExpiredUserOneTimePasswordsCommand user-one-time-password:remove-expired Codziennie 00:40 Usuwa kody OTP, których expired_at < now()

Logika czyszczenia refresh tokenów (CleanupExpiredUserRefreshTokensCommandAction):

UserRefreshToken::where('expires_at', '<', now())
    ->orWhere(function ($query) {
        $query->whereNotNull('revoked_at')->where('updated_at', '<', now()->subDays(7));
    })
    ->delete();

11. Znane ograniczenia i TODO

  1. Brak soft-delete — usunięcie użytkownika jest trwałe (cascade delete na sesje, OTP, refresh tokeny, pracownicy organizacji).
  2. Brak weryfikacji e-mail — kolumna email_verified_at istnieje, ale logika weryfikacji e-mail nie jest w pełni zaimplementowana w domenie User.
  3. Fingerprint backward compatibility — tokeny utworzone przed migracją 2026_02_13_160000_add_fingerprint_to_user_refresh_tokens.php nie mają pola fingerprint, co skutkuje pominięciem walidacji fingerprinta dla starych tokenów.
  4. OTP tylko przez e-mailUserEnumService::ONE_TIME_PASSWORD_CHANNEL_MAIL jest jedynym obsługiwanym kanałem; kod w SendUserOneTimePassword rzuca SendUserOneTimePasswordException dla innych kanałów.
  5. Brak obsługi 2FA przez TOTP/HOTP — system OTP oparty jest wyłącznie na e-mailu, bez wsparcia dla aplikacji uwierzytelniających.
  6. Brak endpointu DELETE /users/{id} — nie ma możliwości usunięcia użytkownika przez API.
  7. Cache organizationIds i managedItemIds — cache jest scope'owany do żądania (array store, rememberForever), co oznacza brak inwalidacji między żądaniami; przy zmianie przypisań organizacji cache jest automatycznie odświeżany przy kolejnym żądaniu.
  8. Throttle: login — limit 5 prób logowania zdefiniowany wewnątrz LoginUserAuthControllerRequest::ensureIsNotRateLimited; konfiguracja rate limitera nie jest widoczna w routach (brak jawnego throttle:X,Y).