Обзор
Подключения — это учетные данные, которыми пользователь располагает для внешнего сервиса (Linear, GitHub, Slack, …). Ваше приложение определяет, как получают эти учетные данные — через провайдера подключения — и использует их во время выполнения для выполнения аутентифицированных вызовов к стороннему API. OAuth 2.0 встроен. Сервисы с собственным потоком подключения — размещенная страница входа, идентификатор аккаунта, передаваемый через вебхук, — используют провайдерcustom, в котором ваше приложение управляет потоком, а Twenty сохраняет пользователя, рабочее пространство и подключение. Будущие типы учетных данных (персональные токены доступа, ключи API, базовая аутентификация) будут подключаться к тому же интерфейсу — приложения, уже использующие defineConnectionProvider({ type: 'oauth', ... }) не потребуют миграции.
defineConnectionProvider
Определите, как в вашем приложении получаются подключения
defineConnectionProvider
Определите, как в вашем приложении получаются подключения
WORKSPACE (по умолчанию) участник нажимает «Добавить подключение» в настройках вашего приложения, завершает процесс на экране согласия провайдера, и в его рабочем пространстве создается запись ConnectedAccount. См. раздел «Подключения рабочего пространства или личные подключения» для провайдеров USER.Рабочей конфигурации нужны два файла — провайдер подключения и соответствующее объявление serverVariables в defineApplication, которое содержит учетные данные клиента OAuth.name— это уникальная строка-идентификатор, используемая вlistConnections({ providerName })(kebab-case, должна соответствовать^[a-z][a-z0-9-]*$).displayNameотображается на вкладке «Общие» приложения и в списке инструментов ИИ.scopeопределяет, кто подключается:'WORKSPACE'(по умолчанию) для подключений, общих для всего рабочего пространства, и'USER'для подключений, которые каждый участник создает для себя. См. ниже раздел «Подключения рабочего пространства или личные подключения».logo— это путь к ресурсу, включенному в папкуpublic/вашего приложения и обслуживаемому через публичные ресурсы приложения. Если параметр не указан, на вкладке «Общие» используется заполнитель, созданный на основеdisplayName.clientIdVariable/clientSecretVariable— это имена, а не значения — они должны совпадать с ключами, объявленными вdefineApplication.serverVariables. Фактическиеclient_idиclient_secretвводятся администратором сервера через интерфейс регистрации приложения и никогда не коммитятся в ваш репозиторий.- Используйте
serverVariables(неapplicationVariables) — учетные данные OAuth являются общими для сервера, и на каждом сервере Twenty используется одно приложение OAuth. - Пока оба
serverVariablesне заполнены, на вкладке «Общие» приложения отображается подсказка «нужен администратор сервера», а кнопка «Добавить подключение» отключена. typeимеет значение'oauth'(с блокомoauth) или'custom'(с блокомcustom, см. «Пользовательские потоки подключения»). Дискриминатор совместим с будущими версиями: будущие типы ('pat','api-key', …) добавит новые блоки подконфигурации наряду с этими.
Пользовательские потоки подключения
Подключайте сервисы, не являющиеся OAuth-провайдерами
Пользовательские потоки подключения
Подключайте сервисы, не являющиеся OAuth-провайдерами
type: 'custom' и двух логических функций:authorizeLogicFunction с callbackUrl, который его содержит. Верните, куда направить пользователя:callbackUrl сервису без изменений. Twenty принимает его как перенаправление браузера (GET) и как уведомление сервера (POST) и запускает callbackLogicFunction для каждого запроса с его методом, параметрами запроса (без state), заголовками, обработанным телом и rawBody (точными байтами запроса, null для GET). Верните аккаунт, который подтверждает запрос, или null, если он не содержит аккаунта — обычно это браузер, возвращающийся в то время, как идентификатор аккаунта передается уведомлением:callbackUrl доказывает только то, что запрос содержит этот URL-адрес, но не то, что его отправил сервис. Когда сервис подписывает свои уведомления, проверяйте подпись для rawBody, прежде чем доверять телу. Заголовки приводятся к нижнему регистру, а cookie, authorization и proxy-authorization никогда не передаются:onConnectLogicFunction не запускается. onConnectLogicFunction запускается при создании, повторном подключении или восстановлении подключения, но не запускается повторно, когда сервис сообщает об уже подключенном аккаунте. Срок действия состояния истекает через час, поэтому уведомление, поступившее позже, отклоняется. Сервисы обычно повторно отправляют уведомления: повтор для уже подключенного аккаунта обновляет это подключение, поэтому повторно вернуть тот же аккаунт безопасно.Twenty не обновляет и не отзывает пользовательские подключения. Освободите то, что сервис хранит для аккаунта, в onDisconnectLogicFunction.type провайдера, как и его scope, нельзя изменить после синхронизации: чтобы перевести сервис между oauth и custom, объявите нового провайдера с новым universalIdentifier.Пользовательские провайдеры доступны в Twenty 2.52.0.Определите подключенную учетную запись
Не допускайте незаметной смены вышестоящих учетных записей при повторном подключении
Определите подключенную учетную запись
Не допускайте незаметной смены вышестоящих учетных записей при повторном подключении
identifyAccountLogicFunction функцию логики в том же приложении, которая возвращает стабильный идентификатор учетной записи, связанной с только что выданным токеном:timeoutSeconds функции значением 10 независимо от того, что указано в манифесте; выполняйте только один запрос к конечной точке провайдера “who am I”. Хук on-connect, если он есть, всё равно запускается позже, после сохранения подключения.Полезная нагрузка. Обработчик получает предварительный токен доступа. Он ещё не сохранён, поэтому getConnection не может его вернуть; используйте его напрямую и не записывайте в журнал.externalAccountIdдолжен быть неизменным на протяжении всего существования вышестоящей учётной записи (id команды, id организации, id пользователя), а не именем, которое пользователь может изменить. Twenty обрезает его и сохраняет в подключении при первом подключении.handle, если он возвращён, заменяет идентификатор, который иначе определил бы Twenty (электронный адрес из OIDCid_tokenили электронный адрес подключающегося пользователя). Верните то, что пользователь узнает, например имя рабочего пространства Slack.- Верните
{ externalAccountId: null }, когда провайдер не может предоставить неизменный id. Тогда Twenty ведёт себя так, как если бы функция идентификации не была объявлена: id не сохраняется, повторное подключение очищает любой ранее сохранённый id, а повторные подключения не проверяются. Простойnullили отсутствие возвращаемого значения считается ошибкой, поэтому обработчик, который забыл вернуть значение, не сможет незаметно отключить проверку. - Если функция генерирует исключение, превышает время ожидания или возвращает что-либо иное, подключение не сохраняется, а пользователь возвращается на страницу настроек с ошибкой. Генерируйте исключение только тогда, когда действительно не можете идентифицировать учётную запись.
externalAccountId с сохранённым в подключении. Если они различаются, обратный вызов отклоняется до внесения каких-либо записей: существующие токены, дескриптор и область действия остаются без изменений, хук подключения не запускается, а пользователь видит, какую учетную запись он авторизовал и какой учетной записи принадлежит подключение. Подключения, созданные до того, как ваше приложение объявило функцию, ещё не имеют сохранённого id; при следующем повторном подключении он будет сохранён.Twenty не устраняет дубликаты по externalAccountId: повторное подключение той же вышестоящей учётной записи через Добавить подключение создаёт два подключения. Id доступен в listConnections и getConnection, поэтому приложение, которому нужно одно подключение для каждой вышестоящей учётной записи, может самостоятельно обнаруживать дубликаты (например, в своём хуке on-connect).Написание функции. Большинство провайдеров предоставляют одну конечную точку, описывающую текущий токен. Для Slack это auth.test, который возвращает рабочее пространство (team_id, team) и пользователя, которому принадлежит токен:String(id).Идентификация доступна в Twenty 2.48.0. Если ваше приложение зависит от неё, объявите "engines": { "twenty": ">=2.48.0" } в его package.json.Запускайте логическую функцию при подключении
Реагируйте в момент установления соединения
Запускайте логическую функцию при подключении
Реагируйте в момент установления соединения
team_id рабочей области, по которому будут определяться входящие события. Установите onConnectLogicFunction, чтобы сослаться на логическую функцию в том же приложении (по её universalIdentifier), и она будет выполнена сразу после создания ConnectedAccount.getConnection(connectedAccountId), чтобы прочитать новый access token и вызвать API провайдера (например, Slack auth.test) или сохранить отображение с помощью key-value store.Запускайте логическую функцию при отключении
Выполняйте очистку при отключении или удалении подключения
Запускайте логическую функцию при отключении
Выполняйте очистку при отключении или удалении подключения
team_id при подключении, должна освободить это право, чтобы другое рабочее пространство могло подключить ту же Slack-команду. Установите onDisconnectLogicFunction, чтобы сослаться на логическую функцию в том же приложении; она выполняется при отключении подключения и повторно при его удалении.ConnectedAccount и его токен всё ещё существуют, поэтому getConnection(connectedAccountId) успешно возвращает результат, и обработчик может отменить всё, что он зарегистрировал удалённо. При удалении учётные данные могут уже отсутствовать (участник может удалить подключение, которое он отключил ранее), поэтому сделайте обработчик идемпотентным и сохраняйте необходимое ему состояние (team_id, идентификатор внешней подписки) в хранилище ключей и значений, записывая его при подключении и используя connectedAccountId в качестве ключа. Не задавайте слишком большое значение: он выполняется в рамках запроса, поэтому его timeoutSeconds задаёт максимальную продолжительность этого запроса.Hook срабатывает, когда соединение отключается или удаляется само по себе. При удалении приложения его соединения удаляются каскадом на уровне базы данных, поэтому hook в этом случае не запускается. Объявите uninstallLogicFunction в defineApplication для этого сценария: она запускается до удаления метаданных приложения, поэтому всё ещё может вызвать listConnections и выполнить оставшуюся очистку.listConnections / getConnection
Используйте подключения из логической функции
listConnections / getConnection
Используйте подключения из логической функции
listConnections({ providerName }) возвращает записи ConnectedAccount этого приложения для указанного провайдера с обновленными токенами доступа.- Передайте
{ providerName }, чтобы отфильтровать по провайдеру; опустите, чтобы получить все подключения этого приложения у всех провайдеров. - Сервер прозрачно обновляет токен доступа перед возвратом. Ваш обработчик всегда получает рабочий токен (или установлено
authFailedAt). getConnection(id)— эквивалент для одной записи.
reportConnectionAuthFailure
Сообщить платформе, что учетные данные недействительны
reportConnectionAuthFailure
Сообщить платформе, что учетные данные недействительны
authFailedAt только при сбое обновления OAuth. Провайдеры, чьи токены никогда не обновляются (например, токен бота Slack), дают сбой только во время вызова внутри ваших обработчиков, поэтому строка подключения в настройках продолжит показывать Подключено, хотя каждый вызов будет завершаться ошибкой. Когда ваш код получает однозначный отказ в аутентификации, сообщите о нём:authFailedAt и необязательную причину для подключения. Строка в настройках изменится на Требуется повторное подключение с кнопкой «Повторно подключить», страница сведений о подключении покажет причину, а getConnection начнёт выбрасывать AppConnectionAuthFailedError для этой строки. Флаг и причина автоматически очищаются, когда пользователь повторно подключается. reason длиной более 1000 символов отклоняется, поэтому сокращайте полезные нагрузки провайдера перед их передачей.Сообщайте только о сбоях, которые действительно исправит повторное подключение (отказе класса invalid_auth от провайдера), и никогда — о временных сетевых ошибках. Приложение может сообщать только о собственных подключениях, а пользователь запроса — только о собственных личных учетных данных.Мутация, лежащая в основе этого помощника, поставляется вместе с Twenty 2.40.0, поэтому её вызов на более старом сервере завершится ошибкой. Если ваше приложение использует её, укажите зависимость в package.json: "engines": { "twenty": ">=2.40.0" }.Подключения рабочего пространства или личные подключения
Выберите, кто подключается, с помощью области действия провайдера
Подключения рабочего пространства или личные подключения
Выберите, кто подключается, с помощью области действия провайдера
- Общее для рабочего пространства (
'WORKSPACE') — любой участник рабочего пространства может использовать эти учетные данные. Триггеры cron/базы данных также видят их, поскольку у них нет пользователя запроса. - Только для меня (
'USER') — учетные данные принадлежат подключающемуся участнику. При запусках от имени участника (триггер HTTP-маршрута сisAuthRequired: true)listConnectionsвозвращает общие подключения и собственные подключения этого участника, аfindConnectionForRequestвыбирает личное подключение участника, переходя к общему подключению, если личного нет. При запусках без пользователя запроса (например, триггеры cron)listConnectionsвозвращает все подключения, включая личные подключения каждого участника.
scope провайдера определяет, какую область действия получат его новые подключения:scope: 'WORKSPACE'(по умолчанию) — приложение работает с подключениями, общими для рабочего пространства. Добавить подключение в настройках рабочего пространства приложения создает общее для рабочего пространства подключение.scope: 'USER'— каждый участник подключает свою учетную запись, создавая личные подключения. Участники добавляют, повторно подключают и удаляют их на странице приложения в разделе Settings > User > Apps preferences, для чего требуется разрешение User Apps. В настройках рабочего пространства приложения нет кнопки Добавить подключение для поставщикаUSER.
USER, поэтому не полагайтесь на него для
функции, которая сейчас нужна каждому рабочему пространству.scope существующего поставщика, и обновление отклоняется, если оно пытается это сделать. Чтобы переместить службу из одной области действия в другую, объявите нового поставщика с новым universalIdentifier и удалите старого. При обновлении старый поставщик и все подключения, созданные с его помощью, удаляются через обычный путь отключения (onDisconnectLogicFunction, отзыв токена, очистка канала), а участники снова подключаются через нового поставщика.Для добавления или повторного подключения соединения Just for me требуется разрешение User Apps, а для добавления или повторного подключения соединения Workspace shared — разрешение Workspace Apps.Используйте подходящее подключение для каждого обработчика:scope, укажите "engines": { "twenty": ">=2.49.0" } в его package.json. В той же версии scope подключения заменяет его visibility, который остается доступным как устаревший псевдоним в нижнем регистре. listConnections и getConnection из twenty-sdk 2.49.0 считывают scope, поэтому приложениям, созданным с его помощью, требуется сервер версии 2.49.0.Единоразовая настройка провайдера
Зарегистрируйте свое приложение OAuth у стороннего сервиса
Единоразовая настройка провайдера
Зарегистрируйте свое приложение OAuth у стороннего сервиса
- Перейдите в настройки разработчика провайдера (например, https://linear.app/settings/api/applications/new).
- Установите Redirect URI в значение
\<SERVER_URL>/auth/apps/callback. - Скопируйте сгенерированные Client ID и Client Secret.
- Откройте установленное приложение в Twenty под учетной записью администратора сервера → задайте значения в соответствующих
serverVariables. - Затем участники рабочего пространства смогут добавлять подключения к провайдерам
WORKSPACEв разделе Подключения конкретного приложения.