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
+4 -11
View File
@@ -60,11 +60,8 @@ type Service struct {
webauthn *webauthn.WebAuthn
// ceremonies — начатые церемонии WebAuthn (в памяти, одноразовые).
ceremonies *ceremonyStore
// oauth — включённые провайдеры (client id/secret из env), oauthRedirectBase —
// внешний адрес инстанса для redirect_uri, stateKey — ключ подписи state.
oauth map[string]oauthClientCredentials
oauthRedirectBase string
stateKey string
stateKey string
}
type instanceSettings interface {
@@ -113,9 +110,8 @@ func New(ctx context.Context, cfg config.Config, st *store.Store, logger *slog.L
sessionTTL = store.SessionTTL
}
// OAuth-провайдеры и RP-конфигурация passkeys: включаются конфигом, при
// RP-конфигурация passkeys: включается конфигом, при
// отсутствии настроек соответствующие ручки отвечают понятной ошибкой.
oauthClients, oauthRedirectBase := oauthFromConfig(cfg, cfg.BaseURL())
service := &Service{
store: st,
@@ -126,11 +122,8 @@ func New(ctx context.Context, cfg config.Config, st *store.Store, logger *slog.L
policy: policy,
cfg: Config{SessionTTL: sessionTTL, MaxLoginFails: 5, LockoutWindow: 15 * time.Minute},
logger: logger,
// state OAuth подписывается секретом сессий: отдельного секрета не нужно.
oauth: oauthClients,
oauthRedirectBase: oauthRedirectBase,
stateKey: cfg.SessionPepper,
ceremonies: newCeremonyStore(),
stateKey: cfg.SessionPepper,
ceremonies: newCeremonyStore(),
}
// Passkeys требуют корректного RP ID: для IP-адреса (стенд без домена)
// браузеры WebAuthn не работают, поэтому такой инстанс просто живёт без них.