SSO и вход через провайдеров

Помимо пароля, Gotcha умеет входить через внешних провайдеров: универсальный OIDC (любой совместимый IdP — Keycloak, Authentik, Auth0 и т.п.), Яндекс ID и VK ID. Каждый провайдер включается независимо переменными окружения сервера — настройка на уровне инсталляции, доступа из UI для неё нет.

Секреты провайдеров, заданных переменными окружения инстанса, живут только в памяти процесса — в базе их нет.

Отдельно от них существует per-org SSO: инстанс-админ настраивает OIDC для конкретной организации через интерфейс, и там client_secret хранится в базе (таблица org_sso), зашифрованным мастер-ключом GOTCHA_SECRET_KEY. Это разные механизмы, и утверждение «секретов в базе нет» относится только к первому.

Общий принцип

Провайдер включается булевым флагом *_ENABLED; если он включён, но обязательные для него переменные (client id/secret и т.п.) не заданы — сервер откажется стартовать с понятной ошибкой конфигурации. Включённые провайдеры появляются кнопками входа на странице логина.

Callback (redirect URI), который нужно зарегистрировать в настройках приложения у провайдера, всегда имеет вид:

{GOTCHA_BASE_URL}/auth/oauth/{provider}/callback

где {provider} — oidc, yandex или vk в зависимости от провайдера, а {GOTCHA_BASE_URL} — тот же адрес, что задан в GOTCHA_BASE_URL сервера (например, https://gotcha.example.com). Например, для generic OIDC это будет https://gotcha.example.com/auth/oauth/oidc/callback. URI не конфигурируется отдельно — он всегда строится по этой схеме, поэтому важно зарегистрировать у провайдера точно такой же адрес.

Яндекс ID и VK ID сами подтверждают адрес пользователя, поэтому по ним разрешены и автосоздание аккаунта, и автопривязка входа к существующему по email. У generic OIDC заявленный email/email_verified может подделать сам IdP — по умолчанию (GOTCHA_OIDC_TRUST_EMAIL=false) вход через него работает только для уже привязанных аккаунтов; самостоятельная регистрация и автопривязка по email включаются явно, см. ниже.

Generic OIDC — пошагово

  1. В консоли своего IdP (Keycloak, Authentik, Auth0, Zitadel и т.п.) заведите новое OAuth/OIDC-приложение (client) типа “confidential”/“web”.
  2. В качестве redirect URI укажите {GOTCHA_BASE_URL}/auth/oauth/oidc/callback — точно так, с вашим реальным GOTCHA_BASE_URL.
  3. Скопируйте Issuer (обычно вида https://idp.example.com/realms/myrealm — базовый адрес, по которому доступен .well-known/openid-configuration), Client ID и Client secret из настроек приложения в IdP.
  4. Задайте переменные окружения сервера:
GOTCHA_OIDC_ENABLED=true
GOTCHA_OIDC_ISSUER=https://idp.example.com/realms/myrealm
GOTCHA_OIDC_CLIENT_ID=<client id из IdP>
GOTCHA_OIDC_CLIENT_SECRET=<client secret из IdP>
GOTCHA_OIDC_SCOPES=openid email profile   # необязательно, это и есть значение по умолчанию
GOTCHA_OIDC_DISPLAY_NAME=Corp SSO                 # необязательно, подпись кнопки на /login (по умолчанию "OIDC")
  1. Перезапустите сервер. На странице /login появится кнопка «Войти через {GOTCHA_OIDC_DISPLAY_NAME или OIDC}».

Gotcha сам сходит по {issuer}/.well-known/openid-configuration за адресами авторизации/токена и JWKS — вручную их указывать не нужно.

По умолчанию этого достаточно только для входа тех, чей аккаунт уже привязан к этому провайдеру. Чтобы через этот же OIDC можно было САМОСТОЯТЕЛЬНО зарегистрироваться (открытая регистрация или приглашение) или автоматически привязать вход к существующему аккаунту по совпадению email, добавьте:

GOTCHA_OIDC_TRUST_EMAIL=true

Включайте это только если IdP — ваш собственный, одно-тенантный (свой Keycloak/Authentik/Zitadel и т.п.), и вы сами управляете тем, кто в нём заводит учётные записи. Не включайте для публичного мульти-тенантного IdP (общий Google-тенант, общедоступный Auth0 и т.п.) — там произвольный человек может завести директорию и указать в ней чужой email, а GOTCHA_OIDC_TRUST_EMAIL=true заставит Gotcha поверить этому на слово и отдать доступ к аккаунту с таким адресом.

Яндекс ID

  1. Зарегистрируйте приложение в Яндекс OAuth (или ID.Yandex — консоль разработчика Яндекса).
  2. Redirect URI: {GOTCHA_BASE_URL}/auth/oauth/yandex/callback.
  3. Скопируйте ID и Пароль (secret) приложения.
  4. Переменные окружения:
GOTCHA_YANDEX_ENABLED=true
GOTCHA_YANDEX_CLIENT_ID=<ID приложения>
GOTCHA_YANDEX_CLIENT_SECRET=<пароль приложения>

Кнопка на /login подписана «Войти через Яндекс».

VK ID

  1. Зарегистрируйте приложение в кабинете VK ID для разработчиков.
  2. Redirect URI: {GOTCHA_BASE_URL}/auth/oauth/vk/callback.
  3. Скопируйте ID приложения и защищённый ключ (client secret).
  4. Переменные окружения:
GOTCHA_VK_ENABLED=true
GOTCHA_VK_CLIENT_ID=<ID приложения>
GOTCHA_VK_CLIENT_SECRET=<защищённый ключ>

Кнопка на /login подписана «Войти через VK».

Как это работает при входе

  • Если email из провайдера уже привязан к существующему аккаунту (или совпадает с verified email существующего пользователя) — вход сразу выпускает сессию.
  • Если аккаунта с таким email ещё нет — дальнейшее зависит от GOTCHA_REGISTRATION_MODE: при open аккаунт появляется при первом входе через провайдера (действующее приглашение на этот email, если оно есть, принимается по пути); при invite новый пользователь провизионируется только по действующему приглашению (см. Приглашение участников) — без него вход отклоняется; при closed новых аккаунтов не появляется вообще.
  • Из профиля (/profile) залогиненный пользователь может дополнительно привязать провайдера к своему существующему аккаунту тем же потоком (?link=1).

Отличие от enterprise-SSO по домену

Отдельно от этих инстанс-уровневых провайдеров у каждой организации есть собственная опциональная секция SSO в /orgs/{id}/settings: свой OIDC-провайдер для входа только участников с определённым доменом email, с опцией «enforced» (принудительный SSO для этого домена — пароль и общие провайдеры перестают работать для таких email). Сама страница настроек доступна owner’у или admin’у организации, но секция SSO внутри неё показывается только owner’у организации, а отдельно от этого — любому, кто помечен как администратор инстанса: это отдельный флаг, не связанный ни с одной ролью в организации, поэтому админ инстанса видит секцию, даже не будучи владельцем этой организации, а admin организации, не являющийся при этом админом инстанса, не видит секцию вовсе. Одной видимости всё равно недостаточно для настройки: включить или удалить федерацию может только аккаунт со статусом администратора инстанса — владелец организации, не являющийся админом инстанса, видит текущий статус SSO, но не форму настройки, и ownership организации никак не влияет на право настраивать/удалять. Разделение существует потому, что само-обслуживаемая настройка SSO владельцем организации на непроверенный домен — вектор захвата чужого аккаунта; подключить домен может только тот, кому оператор инстанса доверяет именно этот домен.

Форма настройки принимает issuer (обязательно https://), client id, client secret, домен (email участников, которых это затрагивает) и роль по умолчанию (member или admin) — роль, которую получит человек, впервые входящий по этому SSO и ещё не состоящий в организации. Redirect URI для регистрации у своего IdP: {GOTCHA_BASE_URL}/auth/oauth/sso-{ID организации}/callback (ID организации виден в адресной строке страницы настроек). IdP обязан подтверждать email (email_verified) — в отличие от инстанс-уровневого generic OIDC, здесь этого нельзя ни включить, ни выключить отдельной переменной: непроверенный email отклоняется всегда, а проверенный email вне указанного домена — тоже (IdP мог его подделать).

Страница /sso — отдельный от /login вход специально для этого механизма: ссылка «Вход через SSO» есть внизу и /login, и /register. Пользователь вводит рабочий email, сервер ищет организацию по домену этого email и, если она нашлась, перенаправляет на её OIDC-провайдера; если для домена SSO не настроен — форма отвечает «SSO для этого домена не настроен» на этой же странице. Как и на /login//register, это отдельный от них поток — попытка войти паролем на /login с email из домена, где enforced=true, отклоняется с сообщением «Ваша организация требует вход через SSO — используйте „Вход через SSO“», и то же сообщение получит попытка зарегистрироваться на /register с таким email.

Что конкретно ломает enforced=true. С момента включения все, у кого email оканчивается на этот домен, теряют вход паролем и общими провайдерами (Яндекс/VK/generic OIDC) целиком — независимо от того, состоят ли они уже в этой организации, и независимо от роли внутри неё: enforced привязан к домену email, а не к членству. Единственный путь входа — /sso с этим доменом. Уже существующий аккаунт с подтверждённым email из этого домена при первом входе через /sso автоматически добавляется в организацию с ролью по умолчанию, если ещё не состоял в ней; если состоял — роль не меняется. Отключить enforced или снять весь SSO-конфиг организации может только администратор инстанса — у самого владельца организации права на это нет (см. выше), поэтому при потере доступа к своему IdP владелец организации не может откатить это переключение сам, только попросить администратора инстанса.

Что дальше

  • Команды и роли — приглашения, без которых новый пользователь через OAuth не заведётся.
  • Конфигурация — остальные серверные переменные окружения.