Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

270 lines
15 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Připojení
description: Umožněte své aplikaci jednat jménem uživatele ve službách třetích stran prostřednictvím OAuth.
icon: plug
---
Připojení jsou pověření, která uživatel uchovává pro externí službu (Linear, GitHub, Slack, ...). Vaše aplikace deklaruje, **jak** se tato pověření získávají — **poskytovatel připojení** — a za běhu je používá k provádění ověřených volání na rozhraní API třetí strany.
V současnosti je podporován pouze OAuth 2.0. Budoucí typy pověření (osobní přístupové tokeny, klíče API, základní autentizace) se připojí ke stejnému rozhraní — aplikace, které již používají `defineConnectionProvider({ type: 'oauth', ... })` nebudou muset migrovat.
<AccordionGroup>
<Accordion title="defineConnectionProvider" description="Definujte, jak vaše aplikace získává připojení">
Poskytovatel připojení popisuje OAuth handshake, který vaše aplikace potřebuje. Uživatel klikne v nastavení vaší aplikace na "Přidat připojení", projde souhlasovou obrazovkou poskytovatele a v jeho pracovním prostoru se vytvoří řádek `ConnectedAccount`.
Funkční nastavení vyžaduje **dva soubory** — poskytovatele připojení a odpovídající deklaraci `serverVariables` v `defineApplication`, která obsahuje klientská pověření OAuth.
```ts src/connection-providers/linear-connection.ts
import { defineConnectionProvider } from 'twenty-sdk/define';
export default defineConnectionProvider({
universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f',
name: 'linear',
displayName: 'Linear',
icon: 'IconBrandLinear',
type: 'oauth',
oauth: {
authorizationEndpoint: 'https://linear.app/oauth/authorize',
tokenEndpoint: 'https://api.linear.app/oauth/token',
scopes: ['read', 'write'],
// These must match keys in `defineApplication.serverVariables` below.
clientIdVariable: 'LINEAR_CLIENT_ID',
clientSecretVariable: 'LINEAR_CLIENT_SECRET',
// Optional: defaults to 'json'. Some providers (Linear, Slack) want
// 'form-urlencoded' for the token request.
tokenRequestContentType: 'form-urlencoded',
// Optional: defaults to true. Disable only if the provider rejects PKCE.
usePkce: false,
// Optional: extra query params on the authorize URL.
// authorizationParams: { prompt: 'consent' },
// Optional: provider's RFC 7009 token revocation endpoint, called on disconnect.
// revokeEndpoint: 'https://example.com/oauth/revoke',
},
// Optional: a logic function in this app to run right after a connection is
// established. See "Run a logic function on connect".
// onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-...-...' },
// Optional: a logic function in this app to run right after a connection is
// removed. See "Run a logic function on disconnect".
// onDisconnectLogicFunction: { universalIdentifier: '4d5e6f70-...-...' },
});
```
```ts src/application.config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '...',
displayName: 'Linear',
description: 'Connect Linear to Twenty.',
// OAuth client credentials live on the app registration (one OAuth app per
// Twenty server, configured by the admin) — not per-workspace. Declare them
// as serverVariables so the admin can fill them in once for all installs.
serverVariables: {
LINEAR_CLIENT_ID: {
description: 'OAuth client ID from your Linear OAuth application.',
isSecret: false,
isRequired: true,
},
LINEAR_CLIENT_SECRET: {
description: 'OAuth client secret from your Linear OAuth application.',
isSecret: true,
isRequired: true,
},
},
});
```
Hlavní body:
* `name` je jedinečný identifikátor (řetězec) používaný v `listConnections({ providerName })` (kebab-case, musí odpovídat `^[a-z][a-z0-9-]*$`).
* `displayName` se zobrazuje na kartě nastavení jednotlivé aplikace a v seznamu nástrojů AI.
* `clientIdVariable` / `clientSecretVariable` jsou názvy, ne hodnoty — musí odpovídat klíčům deklarovaným v `defineApplication.serverVariables`. Skutečné `client_id` a `client_secret` zadává správce serveru prostřednictvím rozhraní pro registraci aplikace, nikdy se necommitují do vašeho repozitáře.
* Použijte `serverVariables` (nikoli `applicationVariables`) — pověření OAuth jsou celoserverová a na jeden server Twenty je jedna aplikace OAuth.
* Dokud nejsou vyplněny obě `serverVariables`, karta nastavení aplikace zobrazuje nápovědu "vyžaduje správce serveru" a tlačítko "Přidat připojení" je zakázané.
* `type: 'oauth'` je dnes jediná podporovaná hodnota. Rozlišovač je kompatibilní do budoucna: budoucí typy (`'pat'`, `'api-key'`, ...) přidají nové podbloky konfigurace vedle `oauth`.
URL zpětného volání OAuth, kterou musí váš poskytovatel zařadit na seznam povolených, je:
```
https://<your-twenty-server>/auth/apps/callback
```
</Accordion>
<Accordion title="Spusťte logickou funkci při připojení" description="Reagujte ve chvíli, kdy je připojení navázáno">
Někteří poskytovatelé vám při připojení předají data, která je potřeba uložit dříve, než lze připojení používat — klasickým příkladem je Slack, kde odpověď OAuth určuje `team_id` pracovního prostoru, podle kterého budou příchozí události indexovány. Nastavte `onConnectLogicFunction` tak, aby odkazovala na logickou funkci ve stejné aplikaci (podle jejího `universalIdentifier`), a ta se spustí hned poté, co je vytvořen `ConnectedAccount`.
```ts src/connection-providers/slack-connection.ts
export default defineConnectionProvider({
universalIdentifier: '...',
name: 'slack',
displayName: 'Slack',
type: 'oauth',
oauth: {
/* ... */
},
// Runs claimSlackTeam after every successful Slack connection.
onConnectLogicFunction: {
universalIdentifier: '3a2b1c0d-1111-4222-8333-444455556666',
},
});
```
Hook běží **asynchronně v připojujícím se pracovním prostoru** (je zařazen do fronty, nečeká se na něj), takže pomalý nebo chybující hook nikdy neblokuje ani nenaruší OAuth callback — udělejte jej idempotentní a zajistěte, aby sám zpracovával opakované pokusy. Obslužná funkce přijímá:
```ts
type OnConnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
Odtud použijte `getConnection(connectedAccountId)` ke čtení čerstvého přístupového tokenu a zavolejte API poskytovatele (např. Slack `auth.test`) nebo uložte mapování pomocí [úložiště klíč–hodnota](/l/cs/developers/extend/apps/logic/key-value-store).
</Accordion>
<Accordion title="Spusťte logickou funkci při odpojení" description="Proveďte vyčištění při odstranění připojení">
Cokoli, co aplikace nárokuje v čase připojení, musí být uvolněno, jakmile připojení zanikne. Integrace Slacku, která si například při připojení nárokuje `team_id`, musí tento nárok uvolnit, aby se mohl jiný pracovní prostor připojit ke stejnému týmu ve Slacku. Nastavte `onDisconnectLogicFunction` tak, aby odkazovala na logickou funkci ve stejné aplikaci, a ta se spustí hned poté, co je `ConnectedAccount` smazán.
```ts src/connection-providers/slack-connection.ts
export default defineConnectionProvider({
universalIdentifier: '...',
name: 'slack',
displayName: 'Slack',
type: 'oauth',
oauth: {
/* ... */
},
// Runs releaseSlackTeam after every Slack disconnection.
onDisconnectLogicFunction: {
universalIdentifier: '4470aba8-5ff5-4800-88db-2a427cd8677c',
},
});
```
Stejně jako hook pro připojení běží **asynchronně v odpojovaném pracovním prostoru** a nikdy neblokuje odpojení. Handler dostává stejný tvar payloadu:
```ts
type OnDisconnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
`ConnectedAccount` už v době spuštění hooku neexistuje, takže `getConnection(connectedAccountId)` už nic nevrátí. Všechno, co čištění potřebuje (například `team_id`, externí ID předplatného), muselo být při připojení zapsáno do [key-value store](/l/cs/developers/extend/apps/logic/key-value-store) pod klíčem `connectedAccountId`.
Hook se spustí, když se připojení odstraní samostatně. Odinstalace aplikace zruší její připojení pomocí kaskádového mazání v databázi, takže se hook v tomto případě nespustí. Pro tento případ deklarujte `uninstallLogicFunction` na `defineApplication`: spustí se před tím, než jsou smazána metadata aplikace, takže může stále volat `listConnections` a dočistit vše, co zůstalo.
</Accordion>
<Accordion title="listConnections / getConnection" description="Použijte připojení z logické funkce">
Uvnitř handleru logické funkce vrací `listConnections({ providerName })` řádky `ConnectedAccount` této aplikace pro daného poskytovatele s obnovenými přístupovými tokeny.
```ts src/logic-functions/handlers/create-linear-issue-handler.ts
import { listConnections } from 'twenty-sdk/logic-function';
export const createLinearIssueHandler = async (input: {
teamId?: string;
title?: string;
}) => {
if (!input.teamId || !input.title) {
return { success: false, error: 'teamId and title are required' };
}
const connections = await listConnections({ providerName: 'linear' });
// Workspace-shared credentials win when present; fall back to the first
// user-visibility one. For HTTP-route triggers you typically pick the
// request user's connection via event.userWorkspaceId instead.
const connection =
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
if (!connection) {
return {
success: false,
error:
'Linear is not connected. Open the app settings and click "Add connection".',
};
}
// Use connection.accessToken to call the third-party API.
const response = await fetch('https://api.linear.app/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${connection.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`,
}),
});
return { success: response.ok };
};
```
Každé připojení má:
| Pole | Popis |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Jedinečné ID řádku; předejte do `getConnection(id)` pro opětovné načtení jednoho záznamu |
| `visibility` | `'user'` (soukromé pro jednoho člena pracovního prostoru) nebo `'workspace'` (sdílené se všemi členy) |
| `scopes` | Oprávnění OAuth udělená poskytovatelem (odlišná od `visibility` — ty spolu nesouvisejí) |
| `userWorkspaceId` | ID `userWorkspace` vlastníka — užitečné pro výběr "připojení uživatele požadavku" ve spouštěčích tras HTTP |
| `accessToken` | Aktuální přístupový token OAuth (v případě vypršení je automaticky obnoven) |
| `name` | Zobrazovaný název připojení (automaticky odvozený při OAuth callbacku, uživatelem přejmenovatelný) |
| `handle` | e-mail, který je připojen na předcházející účet, aktualizován při každém opětovném propojení; vyřešeno z OIDC `id_token` vráceno při výměně tokenu, spadne se zpět na spojovací e-mail dvaceti uživatelů, pokud jej poskytovatel nevrátí. |
| `authFailedAt` | Nastaveno, když poslední obnovení selhalo; uživatel se musí znovu připojit |
Hlavní body:
* Předejte `{ providerName }` pro filtrování podle poskytovatele; vynechejte jej, chcete-li získat všechna připojení, která tato aplikace vlastní napříč všemi poskytovateli.
* Server před vrácením výsledku transparentně obnoví přístupový token. Váš handler vždy uvidí použitelný token (nebo nastavené `authFailedAt`).
* `getConnection(id)` je jednořádkový ekvivalent.
</Accordion>
<Accordion title="Viditelnost pro jednotlivce vs. sdílená v pracovním prostoru" description="Jak si uživatelé vybírají mezi soukromými a sdílenými pověřeními">
Když uživatel klikne na "Přidat připojení", je vyzván k výběru viditelnosti:
* **Jen pro mě** — pověření je soukromé pro připojujícího se uživatele. Jakákoli logická funkce volaná jejich jménem (spouštěč HTTP trasy s `isAuthRequired: true`) jej uvidí; spouštěče cron a události databáze nikoli.
* **Sdíleno v pracovním prostoru** — jakýkoli člen pracovního prostoru může pověření použít. Spouštěče cron/databáze jej také uvidí, protože nemají žádného uživatele požadavku.
Pro každý handler použijte tu správnou variantu:
```ts
// HTTP-route trigger — prefer the request user's own connection.
const conn =
connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ??
connections.find((c) => c.visibility === 'workspace');
// Cron trigger — no request user; only shared credentials are sensible.
const conn = connections.find((c) => c.visibility === 'workspace');
```
Více připojení na (uživatele, poskytovatele) je povoleno, takže tentýž uživatel může mít vedle sebe "Personal Linear" a "Work Linear".
</Accordion>
<Accordion title="Jednorázové nastavení poskytovatele" description="Zaregistrujte svou aplikaci OAuth u služby třetí strany">
Pro každého poskytovatele připojení musí správce serveru nejprve zaregistrovat u třetí strany aplikaci OAuth.
1. Přejděte do vývojářského nastavení poskytovatele (např. https://linear.app/settings/api/applications/new).
2. Nastavte **Redirect URI** na `\<SERVER_URL>/auth/apps/callback`.
3. Zkopírujte vygenerované **Client ID** a **Client Secret**.
4. Otevřete nainstalovanou aplikaci v Twenty jako správce serveru → nastavte hodnoty na odpovídajících `serverVariables`.
5. Členové pracovního prostoru pak mohou přidávat připojení v sekci aplikace **Připojení**.
</Accordion>
</AccordionGroup>