> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Veriqa в рабочих системах

> Как подключить Veriqa к GitLab, Keycloak, Grafana, Nextcloud, Discourse, Odoo и Moodle — что работает, где барьер, чем снимается и что осталось непроверенным.

Кроме CMS Veriqa подключается к системам, куда сотрудник входит каждый день: DevOps, мониторинг,
файловое хранилище, форум, ERP, LMS и чужой identity-provider. Клиент OIDC везде **бесплатный**,
но не везде штатный: GitLab, Keycloak, Grafana, Nextcloud и Discourse несут его в поставке, а
Odoo и Moodle требуют доустановки модуля со стороны.

## Что проверено

| Система | Механизм | Что работает |
| - | - | - |
| GitLab CE 19.3.1 | omniauth `openid_connect` | вход, JIT-провижининг, связывание по `sub` |
| Keycloak 26.7.2 | identity brokering | вход, создание пользователя, связывание по `sub` |
| Grafana OSS 13.0.2 | Generic OAuth | вход, создание пользователя |
| Nextcloud 31.0.14 | приложение `user_oidc` 8.11.0 | вход, создание пользователя, проверка подписи по JWKS |
| Discourse 3.5.0 | `openid_connect`, встроен в ядро | вход **существующего** пользователя, связывание по `sub`; автосоздание не проверено |
| Odoo 18 Community | OCA `auth_oidc` 18.0.1.1.0 | вход, создание пользователя, связывание по `sub`, проверка подписи по JWKS |
| Moodle 5.0.1 | `auth_oidc` 5.0.6 (Microsoft) | вход, создание пользователя, связывание по `sub` |

Проверено на канале **Email**, без изменений на стороне Veriqa.

## GitLab

**Барьер.** Из коробки вход не проходит: `omniauth_openid_connect` кладёт `scope` в запрос
`grant_type=authorization_code`, где [RFC 6749 §4.1.3](https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3)
его не предусматривает. Veriqa отвечает `invalid_request`, GitLab показывает:

```
Could not authenticate you from OpenIDConnect because
"Invalid request :: the 'scope' parameter is not valid in this context."
```

**Что сделать.** Добавить в `args` провайдера один флаг:

```ruby theme={null}
send_scope_to_token_endpoint: false
```

**Учётная запись.** Создаётся первым входом (JIT), связывается по `sub` Veriqa, адрес из claim
`email` GitLab считает **подтверждённым** и своего письма-верификации не шлёт.

<Note>
  Если claim `email` не приходит, GitLab не отказывает: он заводит пользователя, выдаёт сессию и
  подставляет собственный служебный адрес вида `temp-email-for-oauth-{login}@gitlab.localhost`,
  показывая подсказку заполнить профиль.
</Note>

## Keycloak

Здесь Veriqa подключается к **другому identity-provider** как внешний OIDC-провайдер: Keycloak
принимает вход у Veriqa и дальше сам выступает провайдером для своих приложений.

**Конфигурации почти нет.** Keycloak импортирует discovery-документ Veriqa и заполняет все адреса
сам, включая `jwks_uri`, и сам же включает проверку подписи.

**Барьер — не email, а фамилия.** Схема профиля realm'а по умолчанию требует `email`, `firstName`
и `lastName`. Claim `family_name` Veriqa отдаёт только тогда, когда канал дал фамилию: Telegram и
MAX берут её из профиля мессенджера, где она необязательна, а Email не даёт её никогда. Поэтому
первый вход упирается в форму «Update Account Information» — всегда на канале Email и выборочно
на остальных.

**Что сделать.** Realm settings → User profile: снять признак обязательности с `firstName` и
`lastName`.

<Warning>
  Отключение сверки профиля у самого провайдера (`update.profile.on.first.login = off`) эту
  задачу **не решает**, хотя выглядит решением: пользователь создаётся, но Keycloak тут же
  включает обязательное действие `VERIFY_PROFILE` с тем же требованием — учётная запись есть,
  сессии нет. Требование снимается только в схеме профиля realm'а.
</Warning>

**Две формы добора подряд.** Если входу не хватает claim, который Veriqa добирает сама, Veriqa
спрашивает его у пользователя на своей форме, прежде чем вернуть вход. RP со своей формой добора
профиля — Keycloak, а также Logto — показывает сразу после неё вторую. Для такого приложения
выключите форму Veriqa членом `ClaimCompletionFormEnabled: false` в его записи
`Veriqa:OpenIddict:Clients` (см. [Clients](/docs/ru/reference/configuration#clients)). При выключенной
форме недостающие claims не выдаются и вход не блокируют: RP спрашивает их на своей форме. Телефона
и email это не касается — Veriqa получает их своими путями.

## Grafana

**Барьер.** Без claim `email` Grafana не отказывает сразу — она ищет адрес по соглашению,
совместимому с GitHub: запрашивает `{api_url}/emails`, которого в OpenID Connect нет. Получает 404
и прекращает аутентификацию:

```
level=error msg="Error getting email address"
      url=https://<issuer>/connect/userinfo/emails
      error="unsuccessful response status code 404"
```

Сообщение уводит в сторону: причина не в сетевой ошибке, а в отсутствующем claim.

**Что сделать.** Обеспечить приход `email`. Канал Email даёт его сам; на канале, который адреса не
даёт (Telegram), адрес добирает Veriqa внутри входа, если `email` требуется, либо подставляет ваш
код — см. [Обогащение claim'ов](/docs/ru/concepts/claim-enrichment).

**Учётная запись.** Создаётся автоматически, `email`, `name` и `login` заполняются адресом.

<Note>
  Grafana не шлёт `nonce` и не проверяет подпись `id_token` по JWKS — claims она разбирает из тела
  токена. Ещё она пытается прочитать access token Veriqa как JWT и пишет в отладочный лог
  `token is not in JWT format`: access token — не JWT (сейчас это непрозрачный reference-токен, во
  время прогона был JWE), на вход это не влияет.
</Note>

## Nextcloud

Барьеры здесь не в claims, а в транспорте, и оба снимаются настройкой Nextcloud.

**Только HTTPS.** По HTTP `user_oidc` отдаёт 404 со страницей «You must access Nextcloud with HTTPS
to use OpenID Connect». Проверка пропускает поток при HTTPS, в debug-режиме **или** при
app-настройке `allow_insecure_http` — последняя годится для внутреннего контура.

**Защита от приватных адресов.** Пока не выставлен `allow_local_remote_servers=true`, Nextcloud не
ходит на issuer в приватной сети. Диагностируется плохо: возврат отдаёт 403, а единственный след —
строка в `nextcloud.log`:

```
Host <issuer-host> was not connected to because it violates local access rules
```

В продакшне обе настройки не нужны: issuer публичен и работает по HTTPS.

**Связывание — обязательно решить до первого входа.** Идентификатор пользователя Nextcloud строит
по-разному:

| Настройка провайдера | Что попадает в uid |
| - | - |
| `uniqueUid = true` (по умолчанию) | `sha256` от идентификатора провайдера и `sub` — нечитаемый хеш |
| `uniqueUid = false` | `sub` Veriqa дословно |
| `providerBasedId = true` | `{провайдер}-{sub}` |

Если вы планируете сопоставлять учётные записи Nextcloud со своими по `sub`, снимите `uniqueUid`
**до** первого входа: смена настройки не переименовывает уже заведённых пользователей.

<Note>
  Nextcloud проверяет подпись `id_token` по JWKS и **не обращается** к `/connect/userinfo` вовсе:
  все claims он берёт из `id_token`. Claims, которые Veriqa отдаёт только в userinfo, до него не
  доедут — если они вам нужны, включите `enrichLoginIdTokenWithUserinfo`.
</Note>

## Discourse

Плагин `openid_connect` встроен в ядро — устанавливать нечего. Барьера два, и оба снимаются
настройками форума.

**По умолчанию Discourse не запрашивает email.** Настройка `openid_connect_authorize_scope`
содержит только `openid`. Для форума это тупик: без адреса учётная запись не создаётся, а вход
останавливается на форме регистрации. Задайте `openid email profile`.

**Защита от приватных адресов.** Пока issuer в приватной сети, Discourse к нему не пойдёт:
`allowed_internal_hosts` должен содержать его хост. В браузер при этом уезжает безликое
`openid_connect_discovery_error` — причину видно только при включённом
`openid_connect_verbose_logging`. В продакшне барьер не возникает.

<Warning>
  Неудачное чтение discovery **кешируется**. После исправления настройки Discourse какое-то время
  продолжает отвечать той же ошибкой, и выглядит это так, будто правка не помогла. Сбросьте кеш.
</Warning>

**Что проверено и что нет.** Вход существующего пользователя проходит, связка пишется по `sub`
Veriqa, подпись `id_token` Discourse проверяет по JWKS. Автосоздание учётной записи **не
проверено**: у Discourse оно выполняется отдельным запросом из браузера и защищено анти-бот-
механикой.

## Odoo

**Модуль из поставки не подойдёт.** Штатный `auth_oauth` умеет только implicit flow: он просит
`response_type=token`, не шлёт `nonce` и не проверяет подпись. Veriqa отвечает
`unsupported_response_type`: implicit убран из
[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) как небезопасный. Снаружи это выглядит как страница ошибки вместо возврата на сайт.

**Что ставить.** Модуль [`auth_oidc`](https://github.com/OCA/server-auth) из репозитория OCA
(ветка вашей версии Odoo). В настройке провайдера выберите
`flow = OpenID Connect (authorization code flow)` и заполните появившиеся поля: Token URL, JWKS
URL и Client Secret. Именно JWKS URL даёт Odoo проверять подпись Veriqa — и он её проверяет.

<Warning>
  Модуль требует библиотеку `python-jose`, и в официальном образе Odoo её нет. Импорт обёрнут в
  «тихую» ветку с записью в отладочный лог, поэтому без библиотеки модуль ставится, кнопка входа
  появляется, а сам вход отвечает безликим отказом в доступе. Ставьте `python-jose` вместе с
  модулем.
</Warning>

**Учётная запись.** Создаётся автоматически — свободная регистрация включена в чистой базе Odoo
по умолчанию, ничего дополнительно включать не нужно. Связка пишется по `sub` Veriqa в поле
`oauth_uid`, логином становится адрес. Если адрес не приходит, Odoo подставляет собственный
синтетический логин и всё равно заводит пользователя.

## Moodle

Своего OIDC-клиента у Moodle нет; используется плагин
[`auth_oidc`](https://moodle.org/plugins/auth_oidc), который Microsoft поставляет для Entra ID.
Вход проходит, пользователь создаётся, связка пишется по `sub` Veriqa — но три барьера придётся
снять, и два из них вы встретите даже в продакшне.

**Плагин шлёт чужой параметр.** В любом режиме, кроме «Microsoft Identity Platform», он
добавляет к запросу параметр `resource`, а когда поле в настройке пустое — подставляет зашитый
адрес Microsoft Graph. Veriqa отвечает `invalid_target`: Veriqa не регистрирует ресурсы
[RFC 8707](https://www.rfc-editor.org/rfc/rfc8707), поэтому не проходит **никакое** значение.

Выходов два, и тот, что на стороне Veriqa, ничего не требует от Moodle: включите для этого
`client_id` квирк уровня клиента [`drop-resource-parameter`](/docs/ru/guides/client-compatibility) —
параметр снимается до валидации. Потребителя у него здесь нет, поэтому выданный токен совпадает
с тем, что получил бы клиент, никогда его не присылавший: ресурс не регистрируется, audience не
выдаётся.

<Warning>
  Со стороны Moodle обойти это можно только одним способом — выбрать тип провайдера **«Microsoft
  Identity Platform»**, при котором параметр не добавляется. Способ рабочий, но это заведомо
  неверное описание провайдера в настройке: общего режима «другой провайдер» без `resource`
  плагин не даёт. Рекомендуемый путь — квирк выше.
</Warning>

**Защита от приватных адресов — и от портов.** Moodle блокирует исходящие запросы не только по
адресу (`curlsecurityblockedhosts`), но и по порту: `curlsecurityallowedport` по умолчанию
разрешает лишь 443 и 80. В отличие от остальных систем, этот барьер **не исчезает сам в
продакшне** — если ваш issuer слушает нестандартный порт, порт нужно разрешить явно. Диагностика
скупая: в браузере «Error in OpenID Connect. Please check logs», настоящая причина
(`The URL is blocked.`) видна только после включения отладочного режима плагина.

**Двоеточие в идентификаторе.** Если Moodle нечего взять для имени пользователя, кроме `sub`
Veriqa, вход упрётся в «The given username contains invalid characters»: `sub` имеет вид
`telegram:1234` или `email:адрес`, а двоеточие Moodle в логинах не принимает. Два выхода:
запрашивать scope `profile` (тогда имя берётся из `preferred_username`) или включить
`extendedusernamechars`.

**После создания.** Moodle уводит нового пользователя на «дозаполните профиль»: имя и фамилия у
него обязательны, а канал их не всегда даёт.

## Требование email: четыре разных поведения

Telegram адреса не даёт, и системы относятся к его отсутствию по-разному. Разница практическая:
она определяет, нужен ли вам вообще подставленный адрес.

| Поведение | Системы | Нужен ли подставленный адрес |
| - | - | - |
| Отказ до создания учётной записи | Grafana | да |
| Учётная запись создаётся, но сессии нет, пока адрес не введён | Keycloak | нет — снимается настройкой |
| Учётная запись и сессия есть, адрес система подставляет сама | GitLab, Odoo | нет |
| Адрес не требуется, поле остаётся пустым | Moodle | нет |

Как выглядит та же картина на CMS — в [обзоре интеграций с CMS](/docs/ru/integrations/overview).

## Локальная проверка: у каждой системы свой выключатель

Если вы поднимаете связку на своей машине, issuer оказывается в приватной сети, и почти каждая
система защищается от обращений туда собственным флагом:

| Система | Настройка |
| - | - |
| Nextcloud | `allow_local_remote_servers = true` |
| WordPress | `allow_internal_idp = 1` |
| Drupal | `allow_private_issuer` |
| Discourse | `allowed_internal_hosts` |
| Moodle | `curlsecurityblockedhosts` — и отдельно `curlsecurityallowedport` |

Четыре первых барьера исчезают в продакшне вместе с публичным issuer. **Moodle — исключение:**
он ограничивает ещё и порт, поэтому остаётся препятствием и на публичном адресе, если issuer
слушает не 443 и не 80. Подробнее о локальных стендах —
[Локальная проверка](/docs/ru/integrations/local-testing).

## Не проверено

* **Каналы, кроме Email.** На Telegram ожидаемое отличие — требование адреса (см. таблицу выше).
* **Слияние с уже заведённой локальной учётной записью** — кроме Discourse, все системы выше
  проверены на вновь создаваемом пользователе.
* **Роли, группы и права.** Пользователь получает роль по умолчанию; маппинг групп из claim'ов не
  проверялся ни в одной системе.
* **Повторный вход и обновление данных профиля** из провайдера при следующем входе.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.