Перейти до змісту

Сесії, Login/Logout та Власність даних

Для кого: Студенти, що вивчають як Django "пам'ятає" залогіненого юзера між запитами.


1. Чому HTTP не "пам'ятає" юзера?

HTTP — протокол без стану (stateless). Кожен запит — ніби перший. Сервер не знає хто надіслав запит, якщо ти не скажеш.

Аналогія: Уяви магазин де продавець кожного разу забуває тебе. Щоразу треба знову показувати паспорт. Незручно? Django вирішує це через сесії та cookies.


2. Як Django вирішує проблему: Cookies + Sessions

Крок 1: Вхід (Login)

Аліса → POST /login/ { username: 'alice', password: 'secret' }
               ↓
Django: authenticate('alice', 'secret') → перевіряє хеш пароля → OK
               ↓
Django: login() → створює запис сесії в базі даних:
        | session_id         | user_id | created_at |
        | 'xK9mPq...'       |    42   | 2025-01-15 |
               ↓
Відповідь: HTTP 302 Redirect
Headers: Set-Cookie: sessionid=xK9mPq...; HttpOnly; SameSite=Lax

Крок 2: Наступний запит Аліси

Браузер автоматично надсилає: Cookie: sessionid=xK9mPq...
               ↓
SessionMiddleware: знаходить сесію в БД → завантажує
               ↓
AuthenticationMiddleware: user_id=42 → request.user = User(id=42, username='alice')
               ↓
View: request.user вже "знає" що це Аліса ✓

Крок 3: Вихід (Logout)

POST /accounts/logout/ (з CSRF-токеном)
               ↓
Django logout(): видаляє сесію з БД
               ↓
Відповідь: Set-Cookie: sessionid=; expires=Thu, 01 Jan 1970...
               ↓
Браузер видаляє cookie. Аліса стала AnonymousUser.

Діаграма login/logout

sequenceDiagram
    participant Браузер
    participant Django View
    participant Auth Middleware
    participant База даних

    Браузер->>Django View: POST /login/ (логін, пароль)
    Django View->>База даних: authenticate() - перевіряє хеш
    База даних-->>Django View: Повертає User object
    Django View->>База даних: login() - створює сесію
    Django View-->>Браузер: Set-Cookie: sessionid=abc123

    Браузер->>Django View: GET /notes/ з Cookie: sessionid=abc123
    Django View->>Auth Middleware: Хто це?
    Auth Middleware->>База даних: Знаходить сесію
    Auth Middleware-->>Django View: request.user = Alice
    Django View-->>Браузер: Сторінка нотаток Аліси

3. Власність даних та безпечна фільтрація

Головне правило безпеки: Кожен юзер бачить ТІЛЬКИ свої дані.

# НЕБЕЗПЕЧНО — Аліса бачить нотатки всіх юзерів:
def note_list(request):
    notes = Note.objects.all()   # ← Критична помилка!
    return render(request, 'notes.html', {'notes': notes})

# БЕЗПЕЧНО — Аліса бачить тільки свої нотатки:
@login_required
def note_list(request):
    notes = Note.objects.filter(user=request.user)   # ← Завжди фільтруй!
    return render(request, 'notes.html', {'notes': notes})

ER-діаграма: Юзер та його нотатки

erDiagram
    USER ||--o{ NOTE : "власник"
    GROUP ||--o{ NOTE : "групова нотатка"
    USER }o--o{ GROUP : "належить"

    USER {
        int id PK
        string username
        string password_hash
    }
    GROUP {
        int id PK
        string name
    }
    NOTE {
        int id PK
        string title
        text content
        int user_id FK
        int group_id FK
    }

Пояснення зв'язків: - user_id FK — кожна нотатка має власника (обов'язковий) - group_id FK — нотатка може належати групі (необов'язково, null дозволено) - Якщо group_id = NULL → особиста нотатка - Якщо group_id = 5 → групова нотатка, видна всім членам групи 5


4. URL-маршрутизація та вбудовані Views Django

Django надає готові views для login/logout — їх не треба писати самостійно:

# notes_chat_app/urls.py
from django.contrib.auth import views as auth_views

urlpatterns = [
    # Django вбудовані views для auth:
    path('accounts/', include('django.contrib.auth.urls')),
    # Це підключає:
    # /accounts/login/           → LoginView
    # /accounts/logout/          → LogoutView
    # /accounts/password_change/ → PasswordChangeView
    # /accounts/password_reset/  → PasswordResetView
    # ... та інші
]

settings.py — налаштування редиректів:

LOGIN_URL = '/accounts/login/'         # куди перенаправляти незалогінених
LOGIN_REDIRECT_URL = '/notes/'         # куди після успішного входу
LOGOUT_REDIRECT_URL = '/accounts/login/'  # куди після виходу


5. Password Reset Flow — Відновлення паролю

Повний цикл відновлення паролю (вже реалізований у проєкті):

1. /accounts/password_reset/
   → Форма введення email (password_reset_form.html)

2. POST email → Django шукає юзера з цим email
   → Генерує унікальний токен (одноразовий, діє 24 год)
   → Надсилає email з посиланням

   [DEV: email виводиться в консоль — EMAIL_BACKEND = console]

3. /accounts/password_reset/done/
   → Повідомлення "перевір пошту" (password_reset_done.html)

4. /accounts/reset/<uidb64>/<token>/
   → Форма нового пароля (password_reset_confirm.html)
   → Django перевіряє токен. Якщо недійсний → повідомлення про помилку

5. Успішна зміна → /accounts/reset/done/ (password_reset_complete.html)

Де це в нашому проєкті?

Концепція Файл Рядок
login/logout URLs notes_chat_app/urls.py include('django.contrib.auth.urls')
Password reset templates templates/registration/password_reset_*.html всі 5 файлів
Password change templates templates/registration/password_change_*.html 2 файли
Посилання "Забули пароль?" templates/registration/login.html {% url 'password_reset' %}
Посилання "Змінити пароль" templates/layouts/dashboard.html dropdown menu
EMAIL_BACKEND notes_chat_app/settings.py console.EmailBackend