Secrets & Connections
Две разные сущности:
- Secret — произвольная зашифрованная строка (API-key, webhook URL, токен, пароль). Управляется вручную через UI.
- Connection — OAuth-привязка к конкретному провайдеру с автоматической ротацией access/refresh-токенов. Создаётся через OAuth-flow.
Secrets
Section titled “Secrets”Модель
Section titled “Модель”Каждый secret — это:
id(uuid)name(уникален в пределах space’а)description(опционально)ciphertext(AES-256-GCM, encrypted master key’ем space’а)createdAt/updatedAt/archivedAt
Plain-text никогда не хранится. Master key — SECRET_ENCRYPTION_ KEY в .env (32 байта base64), с поддержкой ротации через
SECRET_ENCRYPTION_KEY_VERSION.
Runtime доступ
Section titled “Runtime доступ”В workflow’е secret подтягивается через template:
external.http url = "https://api.vendor.com/data" headers = { Authorization: "Bearer {{secrets.vendor-api-key}}" }Resolver вызывает decryption на runtime’е, plain-text держится в
RAM только на время исполнения step’а. Trace’у secret-значение
не записывается (подменяется [SECRET]).
Операции
Section titled “Операции”| Op | UI | API |
|---|---|---|
| Создать | Settings → Secrets → + New | POST /spaces/<id>/secrets |
| Посмотреть metadata | список в UI | GET /spaces/<id>/secrets |
| Увидеть plain-text | ❌ никогда | ❌ нет API |
| Ротировать | Settings → Secrets → | PUT /secrets/<id>/value |
| Архивировать | Settings → Secrets → | DELETE /secrets/<id> |
Архивированный secret нельзя использовать в новых workflow’ах, но running-run’ы с него дочитывают значение. Нельзя расшифровать ciphertext без master key’а — потеря ключа = потеря секретов (одна из причин нужен key rotation runbook; backlog’ом).
Почему UI не показывает plain-text
Section titled “Почему UI не показывает plain-text”В UX некоторые платформы прячут key за «eye» иконкой → клик раскрывает. Мы это принципиально не делаем:
- Shoulder-surfing (в опене-офисе за вами кто-то смотрит)
- Screenshots / screen recordings (копирование случайное)
- Audit-логи браузера (DevTools ловит секреты в network-tab)
Если вам нужно plain-text (например, экспорт в другой инструмент) — пересоздайте в провайдере + сохраните в новый secret.
Connections
Section titled “Connections”OAuth2 vs. secret
Section titled “OAuth2 vs. secret”Многие вендоры не дают long-lived tokens, только OAuth2 с refresh- циклом (Google / Yandex / Bitrix24 app / Slack / GitHub). Для них OPORA использует отдельную сущность — Connection:
id(uuid)providerKey(google/github/slack/yandex-id/ …)name(человекочитаемое, для UI)externalAccountId(id пользователя у провайдера)accessToken + refreshToken(оба encrypted)expiresAt(когда access expired)scopes(что дал провайдер)revokedAt(nullable — если отозван)
Refresh автоматический
Section titled “Refresh автоматический”OPORA внутри connection-resolver’а проверяет expiresAt перед
каждым handler-call’ом. Если expired — делает refresh через
/token endpoint провайдера, обновляет access/refresh/expiresAt в
БД, потом идёт handler’у.
Пользователь этого не видит — для workflow’а connection выглядит «всегда свежей».
Создание
Section titled “Создание”Connections → + Connect → выбираете провайдера → OPORA
ре-директит на authorize-URL провайдера → вы авторизуетесь →
провайдер ре-директит обратно на /auth/oauth/complete → OPORA
сохраняет tokens.
Connections → connection → Disconnect:
- OPORA вызывает
revokeendpoint провайдера (best-effort; если провайдер не поддерживает — skip) - Ставит
revokedAt = now() - Connection остаётся в БД (для audit’а) но в workflow’ах помечена
как UNAVAILABLE — run с таким connection’ом упадёт с
OAUTH_CONNECTION_REVOKED
Force-refresh (debug)
Section titled “Force-refresh (debug)”Connections → connection → Force-refresh. Полезно когда
провайдер поменял scope’ы и нужно проверить что refresh работает.
Secret vs connection — когда что
Section titled “Secret vs connection — когда что”| Провайдер | Secret | Connection |
|---|---|---|
| Bitrix24 (webhook-URL) | ✅ — URL как secret | (OAuth2 отложен) |
| amoCRM (long-lived) | ✅ — access_token | (OAuth2 отложен) |
| ЮKassa | ✅ — shop_id + secret_key | — |
| Telegram | ✅ — bot token | — |
| Google (Sheets, Calendar) | ❌ | ✅ OAuth2 |
| GitHub | ❌ | ✅ OAuth2 |
| Slack | ❌ | ✅ OAuth2 |
| Yandex ID | ❌ | ✅ OAuth2 |
Правило: если у провайдера есть только OAuth2 — Connection. Если есть long-lived API-key — Secret (проще, не надо refresh-flow перезапускать).
Share между workflows
Section titled “Share между workflows”Secret / connection в пределах одного space’а доступны всем workflow’ам этого space’а. Между space’ами — никогда (даже если вы owner обоих space’ов — это tenant-изоляция).
152-ФЗ
Section titled “152-ФЗ”Secret’ы хранятся локально в Postgres на вашей VM’е. Ничего в Sentry / Resend / внешние SaaS’ы не улетает (PII scrubber проверяет). Это упрощает compliance — всё в РФ-периметре.
Связанное
Section titled “Связанное”- How it works — как connection-resolver живёт на runtime’е
- OAuth providers list — какие connection’ы поддерживаем сейчас