feat: add app-level authentication foundation

This commit is contained in:
a.tolmachev
2026-03-30 23:47:09 +03:00
parent 91854a4153
commit ab2e603997
42 changed files with 1624 additions and 236 deletions
+20 -6
View File
@@ -22,6 +22,7 @@
## 3. Основные ресурсы
- `workspaces`
- `auth`
- `memberships`
- `invitations`
- `operations`
@@ -60,7 +61,20 @@
- `POST /invitations` возвращает metadata invitation и одноразовый `invite_token`;
- `invite_token` доступен только в create-response и не возвращается повторно в list endpoints.
### 5.2. Operations
### 5.2. Auth and session
- `POST /api/auth/login`
- `POST /api/auth/logout`
- `GET /api/auth/session`
Контракт:
- `POST /login` принимает `email` и `password`;
- при успешном логине backend выставляет `HttpOnly` session cookie;
- `GET /session` возвращает текущего пользователя и memberships;
- `POST /logout` инвалидирует текущую session.
### 5.3. Operations
- `GET /api/admin/workspaces/{workspace_id}/operations`
- `POST /api/admin/workspaces/{workspace_id}/operations`
@@ -75,7 +89,7 @@
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}/export`
- `POST /api/admin/workspaces/{workspace_id}/operations/import`
### 5.3. Samples and descriptors
### 5.4. Samples and descriptors
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/input-json`
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/samples/output-json`
@@ -84,7 +98,7 @@
- `POST /api/admin/workspaces/{workspace_id}/operations/{operation_id}/descriptors/descriptor-set`
- `GET /api/admin/workspaces/{workspace_id}/operations/{operation_id}/grpc/services`
### 5.4. Upstream auth profiles
### 5.5. Upstream auth profiles
- `GET /api/admin/workspaces/{workspace_id}/auth-profiles`
- `POST /api/admin/workspaces/{workspace_id}/auth-profiles`
@@ -92,7 +106,7 @@
- `PATCH /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}`
- `DELETE /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}`
### 5.5. Agents
### 5.6. Agents
- `GET /api/admin/workspaces/{workspace_id}/agents`
- `POST /api/admin/workspaces/{workspace_id}/agents`
@@ -105,7 +119,7 @@
- `POST /api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings`
- `DELETE /api/admin/workspaces/{workspace_id}/agents/{agent_id}/bindings/{operation_id}`
### 5.6. Platform API keys
### 5.7. Platform API keys
- `GET /api/admin/workspaces/{workspace_id}/platform-api-keys`
- `POST /api/admin/workspaces/{workspace_id}/platform-api-keys`
@@ -118,7 +132,7 @@
- `secret` доступен только в create-response;
- list endpoints возвращают только metadata, `prefix`, `status`, `scopes` и `last_used_at`.
### 5.7. Observability
### 5.8. Observability
- `GET /api/admin/workspaces/{workspace_id}/logs`
- `GET /api/admin/workspaces/{workspace_id}/logs/{log_id}`
+13 -15
View File
@@ -378,35 +378,33 @@ UI-файлы:
Что уже есть реально:
- внешний `Basic Auth` на `nginx`
- login screen и mock redirect flow
Что отсутствует:
Что уже есть:
- session auth backend;
- login endpoint;
- logout endpoint;
- current user endpoint
Отдельный конфликт:
- это не баг реализации, а отсутствие целого auth слоя;
- login page пока не должна позиционироваться как production-ready flow.
- `POST /api/auth/login`;
- `POST /api/auth/logout`;
- `GET /api/auth/session`;
- protected page guard через backend session.
Простой итог:
- пока не интегрировать как реальный backend flow;
- оставить как временный mock screen до отдельного решения по auth.
- login уже перешел на live backend flow;
- защищенные страницы проверяют server-side session;
- `localStorage` больше не должен быть source of truth для auth.
## 5. Отдельные UI-vs-backend конфликты
### 5.1. Login vs Basic Auth
UI предполагает встроенный login, backend сейчас защищен внешним `Basic Auth`.
Конфликт закрыт. UI использует встроенный login, backend перешел на app-level session auth.
Решение:
- не делать вид, что login уже рабочий;
- отложить интеграцию login до отдельного auth этапа.
- использовать `POST /api/auth/login`, `POST /api/auth/logout`, `GET /api/auth/session`;
- хранить auth state в `HttpOnly` cookie;
- использовать `localStorage` только как UI mirror для display state и не считать его source of truth.
### 5.2. Agent draft/edit lifecycle
+2
View File
@@ -79,6 +79,7 @@ Crank - платформа для публикации внешних API в в
Отдельный слой, не связанный с upstream auth:
- `User`
- `UserSession`
- `Membership`
- `Invitation`
- `PlatformApiKey`
@@ -215,6 +216,7 @@ GraphQL в MCP публикуется как фиксированная опер
- `AgentOperationBinding`
- `AuthProfile`
- `PlatformApiKey`
- `UserSession`
- `InvocationLog`
- `UsageRollup`
+13 -4
View File
@@ -194,12 +194,15 @@
Что уже есть:
- внешний `Basic Auth` на уровне `nginx`.
- mock `login.html` и `login.js`;
- `User`, `Membership`, `Invitation` и `PlatformApiKey` как часть access layer.
Чего не хватает:
- либо собственный auth/session backend;
- либо временный согласованный bridge, если login screen оставляем как demo flow.
- app-level auth/session backend;
- password hash storage;
- session cookie;
- current user endpoint.
## 5. Архитектурные конфликты, которые нужно разобрать отдельно
@@ -232,7 +235,13 @@
Решение:
- зафиксировать временную и целевую auth model отдельно.
- убрать внешний `Basic Auth` как основной способ входа;
- реализовать app-level auth/session backend;
- отделить browser session auth от `PlatformApiKey`.
Статус:
- закрыто в `feat/auth-foundation`.
## 6. Приоритет реализации
+29 -4
View File
@@ -182,10 +182,35 @@
- `id`
- `email`
- `display_name`
- `password_hash`
- `status`
- `created_at`
### 3.9. `Membership`
Пароль:
- хранится только как `Argon2id` hash;
- plaintext пароль не сохраняется;
- верификация использует `password_pepper` из env.
### 3.9. `UserSession`
Поля:
- `id`
- `user_id`
- `secret_hash`
- `status`
- `expires_at`
- `last_seen_at`
- `created_at`
Секрет:
- браузеру выдается только opaque session token;
- в persistent storage сохраняется только `secret_hash`;
- подпись и верификация используют `session_secret` из env.
### 3.10. `Membership`
Поля:
@@ -194,7 +219,7 @@
- `role`
- `created_at`
### 3.10. `InvitationToken`
### 3.11. `InvitationToken`
Поля:
@@ -211,7 +236,7 @@
- полный invite token показывается только один раз при создании;
- в persistent storage сохраняется только `token_hash`.
### 3.11. `InvocationLog`
### 3.12. `InvocationLog`
Продуктовая запись о вызове tool.
@@ -230,7 +255,7 @@
- `response_preview`
- `created_at`
### 3.12. `UsageRollup`
### 3.13. `UsageRollup`
Агрегированная статистика по периоду.
+16
View File
@@ -33,6 +33,7 @@
- `workspaces`
- `users`
- `user_sessions`
- `memberships`
- `invitation_tokens`
- `operations`
@@ -166,9 +167,24 @@
- `id`
- `email`
- `display_name`
- `password_hash`
- `status`
- `created_at`
### `user_sessions`
- `id`
- `user_id`
- `secret_hash`
- `status`
- `expires_at`
- `last_seen_at`
- `created_at`
Ограничения:
- `unique (user_id, id)`
### `memberships`
- `workspace_id`
+1 -1
View File
@@ -221,5 +221,5 @@ CD для MVP должен:
- TLS и renewal сертификатов;
- backup для `postgres` и artifact storage;
- секреты вне репозитория;
- ограничение доступа к `admin-api`;
- app-level auth для `admin-api` и bootstrap admin credentials в env;
- rollback на предыдущий image tag или предыдущую compose revision.
+12 -2
View File
@@ -107,16 +107,26 @@ Local development:
- локальное окружение должно иметь доступ к `PostgreSQL`;
- локальный storage;
- упрощенная auth-модель admin-api.
- app-level auth и bootstrap admin user через `.env`.
Demo/deployment:
- `PostgreSQL`;
- локальный или сетевой storage;
- включенная auth-защита admin-api;
- включенная app-level auth-защита admin-api;
- стабильный `Streamable HTTP` endpoint для MCP.
- containerized runtime через `Docker` и `docker-compose`.
### Auth env
Для app-level auth нужны:
- `CRANK_SESSION_SECRET`
- `CRANK_PASSWORD_PEPPER`
- `CRANK_BOOTSTRAP_ADMIN_EMAIL`
- `CRANK_BOOTSTRAP_ADMIN_PASSWORD`
- `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME`
## 8.1. Delivery artifacts
Для production-like запуска проект должен поставляться с: