feat(auth): вход через OAuth-провайдеры (GitHub, Google, Discord)
Фаза 7, AGENT.md 7.1: провайдеры включаются переменными окружения OAUTH_<PROVIDER>_CLIENT_ID/SECRET, привязка внешнего аккаунта идёт по подтверждённому провайдером email через blind index, вход забаненному на инстансе запрещён, все входы и привязки попадают в события безопасности, привязка — ещё и в аудит инстанса. Внешний идентификатор (subject) хранится только индексом HMAC-SHA-256, токены провайдеров не сохраняются вовсе; state подписывается ключом сессий и ограничен по времени, редирект после входа — только внутренний путь. Если провайдер не настроен, ручка отвечает oauth.provider_not_configured, а клиент показывает человеческий текст вместо кнопки. Клиент: кнопки входа по списку включённых провайдеров на экране входа и раздел «Вход через внешние сервисы» в настройках безопасности; адрес возврата для настроек приложения виден в GET /auth/oauth/providers. Миграция 00019 добавляет таблицу oauth_accounts. Установщик и .env.example знают про OAUTH_* и сохраняют значения при --reconfigure. Тесты: Go — выключенный провайдер, подписанный state (подмена и чужой провайдер), создание и повторный вход, привязка к существующему аккаунту, неподтверждённый email, выключенная регистрация, бан инстанса, mocked провайдер (httptest) для обмена кода; web — кнопки провайдеров и ошибка возврата; серверная ручка отдаёт понятный отказ без настроек.
This commit is contained in:
@@ -636,6 +636,20 @@ generate_secrets() {
|
||||
# расшифровывает email/PII и TOTP-секреты, SESSION_PEPPER — blind index,
|
||||
# LIVEKIT_API_SECRET подписывает токены. Их смена делает существующие
|
||||
# данные нечитаемыми, поэтому она возможна только явным --rotate-secrets.
|
||||
# oauth_value отдаёт значение OAuth-настройки: сначала из окружения
|
||||
# (оператор задал при запуске), затем из существующего .env.
|
||||
oauth_value() {
|
||||
local key="$1"
|
||||
local from_env="${!key:-}"
|
||||
if [ -n "$from_env" ]; then
|
||||
printf '%s' "$from_env"
|
||||
return
|
||||
fi
|
||||
if [ "$env_existing" = "1" ]; then
|
||||
env_get "$key" || true
|
||||
fi
|
||||
}
|
||||
|
||||
secret_or_new() {
|
||||
local key="$1" generator="${2:-rand_hex}"
|
||||
local current=""
|
||||
@@ -669,6 +683,17 @@ generate_secrets() {
|
||||
LIVEKIT_API_KEY="$(secret_or_new LIVEKIT_API_KEY rand_hex)"
|
||||
LIVEKIT_API_SECRET="$(secret_or_new LIVEKIT_API_SECRET rand_hex)"
|
||||
|
||||
# OAuth (Фаза 7) не генерируется: клиентские id/секреты выдаёт провайдер.
|
||||
# Значение берём из окружения, иначе сохраняем прежнее из .env — иначе
|
||||
# `--reconfigure` молча выключил бы вход через провайдеров.
|
||||
OAUTH_GITHUB_CLIENT_ID="$(oauth_value OAUTH_GITHUB_CLIENT_ID)"
|
||||
OAUTH_GITHUB_CLIENT_SECRET="$(oauth_value OAUTH_GITHUB_CLIENT_SECRET)"
|
||||
OAUTH_GOOGLE_CLIENT_ID="$(oauth_value OAUTH_GOOGLE_CLIENT_ID)"
|
||||
OAUTH_GOOGLE_CLIENT_SECRET="$(oauth_value OAUTH_GOOGLE_CLIENT_SECRET)"
|
||||
OAUTH_DISCORD_CLIENT_ID="$(oauth_value OAUTH_DISCORD_CLIENT_ID)"
|
||||
OAUTH_DISCORD_CLIENT_SECRET="$(oauth_value OAUTH_DISCORD_CLIENT_SECRET)"
|
||||
OAUTH_REDIRECT_BASE="$(oauth_value OAUTH_REDIRECT_BASE)"
|
||||
|
||||
if [ "$DRY_RUN" = "1" ]; then
|
||||
log_file_only "DRY-RUN: секреты сгенерированы в памяти (в файл не пишутся)"
|
||||
ok "секреты подготовлены (dry-run)"
|
||||
@@ -830,6 +855,17 @@ BACKUP_OFFSITE_TARGET=@BACKUP_OFFSITE_TARGET@
|
||||
BACKUP_OFFSITE_KEEP_DAYS=@BACKUP_OFFSITE_KEEP_DAYS@
|
||||
AGE_RECIPIENT=@AGE_RECIPIENT@
|
||||
|
||||
# --- OAuth-провайдеры (Фаза 7) ---
|
||||
# Пусто — провайдер выключен. Адрес возврата: <домен>/api/v1/auth/oauth/<id>/callback.
|
||||
OAUTH_GITHUB_CLIENT_ID=@OAUTH_GITHUB_CLIENT_ID@
|
||||
OAUTH_GITHUB_CLIENT_SECRET=@OAUTH_GITHUB_CLIENT_SECRET@
|
||||
OAUTH_GOOGLE_CLIENT_ID=@OAUTH_GOOGLE_CLIENT_ID@
|
||||
OAUTH_GOOGLE_CLIENT_SECRET=@OAUTH_GOOGLE_CLIENT_SECRET@
|
||||
OAUTH_DISCORD_CLIENT_ID=@OAUTH_DISCORD_CLIENT_ID@
|
||||
OAUTH_DISCORD_CLIENT_SECRET=@OAUTH_DISCORD_CLIENT_SECRET@
|
||||
# Необязательно: внешний адрес инстанса для redirect_uri (пусто — из домена).
|
||||
OAUTH_REDIRECT_BASE=@OAUTH_REDIRECT_BASE@
|
||||
|
||||
# --- функции ---
|
||||
REGISTRATION_ENABLED=@REGISTRATION_ENABLED@
|
||||
ANTI_BOT_ENABLED=@ANTI_BOT_ENABLED@
|
||||
@@ -899,6 +935,16 @@ LIVEKIT_API_KEY=${LIVEKIT_API_KEY}
|
||||
LIVEKIT_API_SECRET=${LIVEKIT_API_SECRET}
|
||||
LIVEKIT_PUBLIC_URL=${LIVEKIT_PUBLIC_URL}
|
||||
|
||||
# OAuth-провайдеры (Фаза 7): пусто — провайдер выключен. Кавычки обязательны:
|
||||
# секреты провайдеров содержат символы, значимые для оболочки.
|
||||
OAUTH_GITHUB_CLIENT_ID="${OAUTH_GITHUB_CLIENT_ID:-}"
|
||||
OAUTH_GITHUB_CLIENT_SECRET="${OAUTH_GITHUB_CLIENT_SECRET:-}"
|
||||
OAUTH_GOOGLE_CLIENT_ID="${OAUTH_GOOGLE_CLIENT_ID:-}"
|
||||
OAUTH_GOOGLE_CLIENT_SECRET="${OAUTH_GOOGLE_CLIENT_SECRET:-}"
|
||||
OAUTH_DISCORD_CLIENT_ID="${OAUTH_DISCORD_CLIENT_ID:-}"
|
||||
OAUTH_DISCORD_CLIENT_SECRET="${OAUTH_DISCORD_CLIENT_SECRET:-}"
|
||||
OAUTH_REDIRECT_BASE="${OAUTH_REDIRECT_BASE:-}"
|
||||
|
||||
# Offsite-бэкапы: цель и срок хранения задаются вручную после установки.
|
||||
BACKUP_OFFSITE_TARGET=${BACKUP_OFFSITE_TARGET:-}
|
||||
BACKUP_OFFSITE_KEEP_DAYS=${BACKUP_OFFSITE_KEEP_DAYS:-}
|
||||
|
||||
Reference in New Issue
Block a user