chore(openspec): archive completed changes and sync main specs
- archive local-fs-storage-backend, sqlite-metadata-backend, storage-orphan-reconciliation, metadata-managed-jwt-secret, default-local-backends - sync specs: storage-backends, metadata-backends, storage-reconciliation, app-secrets; update photos/share-links - defaults reflected in specs: SQLite and local FS
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
См. `proposal.md` — Why. Текущее состояние:
|
||||
|
||||
- `JWT_SECRET` обязателен в env (`config.Load`), используется в `auth.NewTokenManager`
|
||||
(HS256-подпись access-токенов) и как fallback для `URL_SECRET` в `storage.Open`.
|
||||
- Refresh-токены не подписываются секретом (случайные, хранятся хэшем).
|
||||
- Есть два бэкенда метаданных (`postgres`, `sqlite`) за интерфейсом `Repository` и
|
||||
фабрикой `store.NewPostgres/NewSQLite`; миграции раздельные (pg-набор и `migrations/sqlite`).
|
||||
- Порядок старта в `cmd/server`: миграции → метаданные → storage → app.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Хранить секрет приложения в слое метаданных; генерировать при первом использовании.
|
||||
- Убрать обязательный `JWT_SECRET` из окружения.
|
||||
- Единый стабильный секрет на инсталляцию; корректное поведение при конкурентном старте.
|
||||
|
||||
**Non-Goals:**
|
||||
- Шифрование секрета в БД (БД доверенная).
|
||||
- UI/команда ротации секрета.
|
||||
- Миграция существующих env-секретов в БД.
|
||||
- Изменение формата токенов или наблюдаемого поведения аутентификации для клиента.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1. Таблица `app_settings`
|
||||
`app_settings(key TEXT PRIMARY KEY, value TEXT NOT NULL, created_at, updated_at)` в обоих
|
||||
бэкендах. Ключ `jwt_secret`. Отдельные миграции: PostgreSQL `000010_app_settings`,
|
||||
SQLite `000002_app_settings` (консолидированная 000001 уже применена на существующих БД).
|
||||
|
||||
### D2. Репозиторий `internal/settings`
|
||||
Интерфейс `Repository { Get(ctx,key) (value string, ok bool, err error); Set(ctx,key,value) error; Ensure(ctx,key, generate func() (string,error)) (string, error) }`.
|
||||
Реализации pg и sqlite. `Ensure`: `SELECT` → если есть, вернуть; иначе сгенерировать,
|
||||
`INSERT ... ON CONFLICT(key) DO NOTHING`, затем `SELECT` (единый секрет при гонке).
|
||||
Добавляется в `store.Repos`.
|
||||
|
||||
### D3. Генерация секрета
|
||||
`crypto/rand` 32 байта → base64url (без padding). Значение хранится как строка; в
|
||||
TokenManager и Signer передаётся как есть.
|
||||
|
||||
### D4. Порядок старта и проводка
|
||||
`cmd/server`: миграции → метаданные → `secret := repos.Settings.Ensure("jwt_secret", generate)`
|
||||
→ `tm := auth.NewTokenManager(secret, accessTTL, refreshTTL)` → `storage.Open(ctx, cfg, secret)`
|
||||
(где `URL_SECRET`, если задан, имеет приоритет, иначе используется `secret`) → `app.New`.
|
||||
`app.New` перестаёт строить TokenManager из конфига и принимает готовый `*auth.TokenManager`
|
||||
(или секрет).
|
||||
|
||||
### D5. Конфиг
|
||||
Поле `Auth.JWTSecret` и проверка обязательности удаляются; `JWT_ACCESS_TTL`/`JWT_REFRESH_TTL`
|
||||
остаются. `URL_SECRET` остаётся необязательным. `storage.Open` получает секрет параметром
|
||||
вместо чтения из конфига.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [Смена источника секрета у существующих деплоев] → access-токены инвалидируются, но
|
||||
refresh-токены остаются валидными (не подписаны секретом), клиент молча обновляет access;
|
||||
в худшем случае — перелогин. Отмечаем в доках.
|
||||
- [Конкурентный первый старт на общей БД] → `INSERT ... ON CONFLICT DO NOTHING` + повторный
|
||||
`SELECT` (D2).
|
||||
- [Секрет в открытом виде в БД] → БД доверенная; шифрование — вне рамок.
|
||||
- [Тихая смена поведения] → при `URL_SECRET` не задан подпись URL зависит от секрета из БД;
|
||||
явный `URL_SECRET` снимает зависимость.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Добавить таблицу и репозиторий секретов; сид отсутствует — генерируется при старте.
|
||||
2. Переключить `cmd/server`/`app`/`storage` на секрет из метаданных; убрать `JWT_SECRET`.
|
||||
3. Обновить доки и `.env.example`.
|
||||
4. Откат: вернуть чтение `JWT_SECRET` из env (код), данные не меняются.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Нужна ли операция ротации секрета (сброс значения и инвалидация access-токенов).
|
||||
Reference in New Issue
Block a user