refactor: убрать вход через внешние провайдеры (OAuth) полностью

Решение владельца 27.09.2026 (D-084): сервис ставится на сервер конкретного
человека, и ему всё равно нужен свой OAuth у провайдера — поддержка общего
входа только создаёт трение (регистрация приложений, redirect URI, модерация,
чужие ключи в конфиге). Способы входа остаются: пароль + 2FA и ключи доступа
(WebAuthn).

Удалено:
- сервер: internal/auth/oauth.go, internal/server/oauth.go, internal/store/oauth.go
  и их тесты; поля и методы конфига OAuth*; oauthLimiter и регистрация ручек;
  отображение ошибок oauth.*; features.oauth_enabled/oauth_providers в /meta;
- клиент: web/src/api/oauth.ts, раздел «Вход через внешние сервисы», кнопки
  провайдеров на странице входа, ключи i18n (ru/en), тесты и фикстуры;
- установщик: переменные OAUTH_* из .env, .env.example и шаблона (хелпер
  чтения существующих значений переименован в existing_value — он остался нужен
  для VAPID_SUBJECT);
- зависимость golang.org/x/oauth2 (go mod tidy).

Схема: миграция 00027 удаляет таблицу oauth_accounts (00019 не переписываем —
она применена на стендах). Откат миграции возвращает структуру; тест
TestOAuthRemovalMigration проверяет накат, откат и повторный накат.

AGENT.md (локальный) помечает пункты про OAuth как отменённые.
This commit is contained in:
2026-09-27 18:27:33 +03:00
parent c88f774749
commit a48b865bc9
27 changed files with 100 additions and 2287 deletions
-19
View File
@@ -81,25 +81,6 @@ func newAPIError(err error) apiError {
case errors.Is(err, auth.ErrPasskeyLimit):
candidate.Status, candidate.Code = http.StatusConflict, "auth.passkey_limit"
candidate.Message = "passkey limit reached for this account"
// OAuth-провайдеры (Фаза 7).
case errors.Is(err, auth.ErrOAuthNotConfigured):
candidate.Status, candidate.Code = http.StatusNotFound, "oauth.provider_not_configured"
candidate.Message = "oauth provider is not configured on this instance"
case errors.Is(err, auth.ErrOAuthUnknownProvider):
candidate.Status, candidate.Code = http.StatusNotFound, "oauth.provider_unknown"
candidate.Message = "unknown oauth provider"
case errors.Is(err, auth.ErrOAuthState):
candidate.Status, candidate.Code = http.StatusBadRequest, "oauth.state_invalid"
candidate.Message = "oauth state is invalid or expired"
case errors.Is(err, auth.ErrOAuthEmailUnverified):
candidate.Status, candidate.Code = http.StatusForbidden, "oauth.email_unverified"
candidate.Message = "provider did not confirm the email address"
case errors.Is(err, auth.ErrOAuthEmailMissing):
candidate.Status, candidate.Code = http.StatusForbidden, "oauth.email_missing"
candidate.Message = "provider did not return an email address"
case errors.Is(err, auth.ErrOAuthExchange):
candidate.Status, candidate.Code = http.StatusBadGateway, "oauth.exchange_failed"
candidate.Message = "oauth provider rejected the request"
case errors.Is(err, store.ErrNotFound):
candidate.Status, candidate.Code, candidate.Message = http.StatusNotFound, "not_found", "resource not found"
case errors.Is(err, store.ErrConflict):