docs: детализация дизайна Фазы 2 (авторизация)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 19:33:12 +07:00
parent 136708bdf0
commit fc5d3cdbae
@@ -310,3 +310,78 @@ web/
### Разбивка ### Разбивка
Один план `phase1c-react-spa`. Один план `phase1c-react-spa`.
## Фаза 2 — детализация (авторизация)
Снимает `DEFAULT_PROJECT_ID`, вводит регистрацию/логин и мультитенантность по пользователям.
Закрывает IDOR из 1B (доступ к чужим ресурсам).
### Решения
- **Cookie-сессии в БД**: `httpOnly + Secure + SameSite=Lax` cookie; в БД хранится **хэш** токена
(sha256), не сам токен. Random-токен через `crypto/rand` (32 байта, base64url).
- **Email + пароль**: хэш пароля **argon2id** (`golang.org/x/crypto/argon2`; salt 16 байт,
time=13, memory=64MB, threads=4, keyLen=32; формат `$argon2id$...`).
- **Без email-верификации** на старте (подтверждение/сброс пароля — позже).
- **Владелец = пользователь**: без шаринга и ролей. **Один проект на пользователя** (создаётся
при регистрации); много доменов/шаблонов/учёток внутри. Project switcher — позже.
- CSRF: same-origin + `SameSite=Lax` на старте; отдельный CSRF-токен — backlog.
### БД (миграция 0003)
```
ALTER TABLE users ADD COLUMN password_hash text; -- nullable: seed-user остаётся техническим
CREATE TABLE sessions (
id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash text NOT NULL UNIQUE, -- sha256(token)
expires_at timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ON sessions (token_hash);
```
### Backend
```
internal/auth
├─ password.go — argon2id Hash(password) / Verify(hash, password)
└─ session.go — SessionStore: Create(userID)→(token,expires), Validate(token)→userID, Delete(token)
internal/api/auth_handlers.go — register / login / logout / me
internal/api/middleware.go — RequireAuth (session→userID в контекст), RequireProjectAccess (pid→owner)
```
- `POST /api/v1/auth/register` `{email,password}` — транзакция: создать `user` (password_hash) +
его `project` → создать сессию → `Set-Cookie`. Ответ: `{user, project}` (без хэша).
- `POST /api/v1/auth/login` `{email,password}``GetUserByEmail` → argon2 Verify → сессия → cookie.
Ошибка входа — единый ответ 401 (не раскрывать, email или пароль неверны).
- `POST /api/v1/auth/logout` — удалить сессию, очистить cookie.
- `GET /api/v1/auth/me` — из сессии: `{user, project}`; 401 если не авторизован.
- **RequireAuth** middleware: cookie → token → sha256 → `GetSessionByTokenHash` → проверка `expires_at`
`userID` в контекст; иначе 401. Защищает `/api/v1/projects/*` и `/auth/me`, `/auth/logout`.
- **RequireProjectAccess** middleware на `/projects/{pid}/*`: `GetProject(pid, userID)` — если проект
не принадлежит пользователю → 404 (не 403, чтобы не раскрывать существование). Закрывает IDOR.
- **Рефакторинг 1B под tenant scope**: `LoadDomainFull` получает `AND d.project_id = $2`;
`service.Check/Apply` принимают `projectID`; хендлеры check/apply/CRUD берут `pid` из контекста
(валидированный middleware), а не «как есть».
### Frontend
- `AuthContext` (`{user, project, loading, login, register, logout}`) — при старте `GET /auth/me`
(cookie) → user+project или неавторизован.
- API-клиент: `credentials:'include'`; `API_BASE` строится из активного `projectID` **из контекста**
(зашитый `DEFAULT_PROJECT_ID` удаляется); 401 → выход в `/login`.
- Страницы `LoginPage`, `RegisterPage` (email+пароль, zod-валидация); `logout` в `Layout`.
- **Protected routes**: неавторизованный → `/login`; авторизованный на `/login``/domains`.
### Тестирование Фазы 2
- `auth` — юниты: argon2id round-trip + неверный пароль; session Create/Validate/Delete, истечение.
- Auth-хендлеры — httptest + store (register создаёт user+project+session; login/logout; me).
- Middleware — RequireAuth (нет/битая/истёкшая сессия → 401); RequireProjectAccess (чужой pid → 404).
- IDOR-регресс: пользователь A не может check/apply/CRUD домен пользователя B.
- Frontend — AuthContext (me/login/logout), protected-route редиректы, 401-обработка, клиент шлёт cookie.
### Разбивка
Один план `phase2-auth`.