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
16 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: Bağlantılar
description: Uygulamanızın, OAuth aracılığıyla üçüncü taraf hizmetlerde kullanıcı adına işlem yapmasına izin verin.
icon: plug
---
Bağlantılar, bir kullanıcının harici bir hizmet için (Linear, GitHub, Slack, ...) sahip olduğu kimlik bilgileridir. Uygulamanız bu kimlik bilgilerinin **nasıl** elde edildiğini — bir **bağlantı sağlayıcısı** — bildirir ve çalışma zamanında üçüncü taraf API'sine kimlik doğrulamalı çağrılar yapmak için bunları kullanır.
Bugün yalnızca OAuth 2.0 destekleniyor. Gelecekteki kimlik bilgisi türleri (kişisel erişim belirteçleri, API anahtarları, basic auth) aynı yüzeye bağlanacak — halihazırda `defineConnectionProvider({ type: 'oauth', ... })` kullanan uygulamaların geçiş yapması gerekmeyecek.
<AccordionGroup>
<Accordion title="defineConnectionProvider" description="Uygulamanızın bağlantılarının nasıl elde edildiğini belirtin">
Bir bağlantı sağlayıcısı, uygulamanızın ihtiyaç duyduğu OAuth el sıkışmasını açıklar. Kullanıcı, uygulamanızın ayarlarında "Bağlantı ekle"ye tıklar, sağlayıcının izin ekranını tamamlar ve çalışma alanında bir `ConnectedAccount` satırı oluşturulur.
Çalışan bir kurulum **iki dosya** gerektirir — bağlantı sağlayıcısı ve OAuth istemci kimlik bilgilerini tutan `defineApplication` üzerindeki eşleşen bir `serverVariables` bildirimi.
```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,
},
},
});
```
Önemli noktalar:
* `name`, `listConnections({ providerName })` içinde kullanılan benzersiz tanımlayıcı dizedir (kebab-case, `^[a-z][a-z0-9-]*$` ile eşleşmelidir).
* `displayName` uygulama başına ayarlar sekmesinde ve Yapay Zeka araç listesinde gösterilir.
* `clientIdVariable` / `clientSecretVariable` değer değil, **isimdir** — `defineApplication.serverVariables` içinde bildirilen anahtarlarla eşleşmelidir. Gerçek `client_id` ve `client_secret`, sunucu yöneticisi tarafından uygulama kayıt arayüzü üzerinden girilir; deponuza asla commit edilmez.
* `serverVariables` kullanın (`applicationVariables` değil) — OAuth kimlik bilgileri sunucu genelidir ve her Twenty sunucusu için bir OAuth uygulaması vardır.
* Her iki `serverVariables` da doldurulana kadar, uygulama başına ayarlar sekmesi "sunucu yöneticisine ihtiyaç var" ipucunu gösterir ve "Bağlantı ekle" düğmesi devre dışı bırakılır.
* `type: 'oauth'` bugün desteklenen tek değerdir. Ayırt edici alan ileriye dönük uyumludur: gelecekteki türler (`'pat'`, `'api-key'`, ...) `oauth` yanında yeni alt yapılandırma blokları eklenecektir.
Sağlayıcınızın beyaz listeye alması gereken OAuth geri çağrı URL'si şudur:
```
https://<your-twenty-server>/auth/apps/callback
```
</Accordion>
<Accordion title="Bağlanıldığında bir mantık fonksiyonu çalıştırın" description="Bir bağlantı kurulur kurulmaz tepki verin">
Bazı sağlayıcılar, bağlantı anında size, bağlantı kullanılabilir hale gelmeden önce kalıcı hale getirmeniz gereken veriler verir — klasik örnek Slack'tir; burada OAuth yanıtı, gelen olayların anahtarlanacağı çalışma alanının `team_id` bilgisini sağlar. `onConnectLogicFunction` değerini, aynı uygulamadaki bir mantık fonksiyonuna (onun `universalIdentifier` değeriyle) referans verecek şekilde ayarlayın; böylece `ConnectedAccount` oluşturulduktan hemen sonra çalışır.
```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',
},
});
```
Kanca, **bağlanan çalışma alanında eşzamansız olarak** çalışır (kuyruğa alınır, beklenmez), bu nedenle yavaşlayan veya başarısız olan bir kanca hiçbir zaman OAuth geri çağrısını engellemez veya bozmaz — onu idempotent yapın ve kendi tekrar denemelerini kendisi yönetsin. İşleyici şunları alır:
```ts
type OnConnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
Buradan `getConnection(connectedAccountId)` kullanarak yeni erişim belirtecini okuyun ve sağlayıcının API'sini (ör. Slack `auth.test`) çağırın veya [key-value store](/l/tr/developers/extend/apps/logic/key-value-store) ile bir eşleme kalıcı hale getirin.
</Accordion>
<Accordion title="Bağlantı kesildiğinde bir mantık fonksiyonu çalıştırın" description="Bir bağlantı kaldırıldığında temizleme işlemi yapın">
Bir uygulamanın bağlanma anında talep ettiği her şey, bağlantı sona erdiğinde serbest bırakılmalıdır. Örneğin, bağlanırken bir `team_id` talep eden bir Slack entegrasyonu, başka bir çalışma alanının aynı Slack ekibini bağlayabilmesi için bu talebi serbest bırakmak zorundadır. Aynı uygulamadaki bir mantık fonksiyonuna referans vermesi için `onDisconnectLogicFunction` değerini ayarlayın; böylece `ConnectedAccount` silindikten hemen sonra çalışır.
```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',
},
});
```
Bağlanma kancası gibi, **bağlantısı kesilmekte olan çalışma alanında eşzamansız olarak** çalışır ve bağlantı kesme işlemini asla engellemez. İşleyici aynı payload biçimini alır:
```ts
type OnDisconnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
Kanca çalıştığında `ConnectedAccount` zaten kaldırılmıştır, bu yüzden `getConnection(connectedAccountId)` artık sonuç döndürmez. Temizliğin ihtiyaç duyduğu her şey (`team_id`, harici abonelik kimliği gibi) bağlanma sırasında, `connectedAccountId` anahtarıyla [key-value store](/l/tr/developers/extend/apps/logic/key-value-store) içine yazılmış olmalıdır.
Kanca, bir bağlantı kendi başına kaldırıldığında tetiklenir. Uygulamanın kaldırılması, bağlantılarını bunun yerine bir veritabanı zincirleme silme işlemiyle kaldırır, bu nedenle kanca burada çalışmaz. Bu yol için `defineApplication` üzerinde bir `uninstallLogicFunction` bildirin: bu işlev, uygulamanın meta verileri silinmeden önce çalışır, böylece hâlâ `listConnections` çağırabilir ve geride kalan her şeyi temizleyebilir.
</Accordion>
<Accordion title="listConnections / getConnection" description="Bir mantık işlevinden bağlantıları kullanın">
Bir mantık işlevi işleyicisi içinde, `listConnections({ providerName })`, verilen sağlayıcı için bu uygulamanın `ConnectedAccount` satırlarını, yenilenmiş erişim belirteçleriyle döndürür.
```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 };
};
```
Her bağlantı şunlara sahiptir:
| Alan | Açıklama |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | Tekil satır kimliği; tek bir tanesini yeniden getirmek için `getConnection(id)` işlevine iletin |
| `visibility` | `'user'` (bir çalışma alanı üyesine özel) veya `'workspace'` (tüm üyelerle paylaşılan) |
| `scopes` | Üst sağlayıcı tarafından verilen OAuth izinleri (`visibility` ile karıştırılmamalıdır — bunlar ilişkili değildir) |
| `userWorkspaceId` | Sahibinin userWorkspace kimliği — HTTP rota tetikleyicilerinde "istek kullanıcısının bağlantısını" seçmek için kullanışlıdır |
| `accessToken` | Yeni OAuth erişim belirteci (süresi dolmuşsa otomatik olarak yenilenir) |
| `name` | Bağlantının görünen adı (OAuth geri çağrısında otomatik türetilir, kullanıcı tarafından yeniden adlandırılabilir) |
| `tanıtıcı` | Bağlı yukarı akış hesabının e-postası, her yeniden bağlanmada yenilenir; jeton değişiminde döndürülen OIDC `id_token` içinden belirlenir ve sağlayıcı bir e-posta döndürmezse bağlanan Twenty kullanıcısının e-postasına geri düşer. |
| `authFailedAt` | En son yenileme başarısız olduğunda ayarlanır; kullanıcı yeniden bağlanmalıdır |
Önemli noktalar:
* Sağlayıcıya göre filtrelemek için `{ providerName }` iletin; bu uygulamanın tüm sağlayıcılardaki tüm bağlantılarını almak için bunu atlayın.
* Sunucu, döndürmeden önce erişim belirtecini şeffaf bir şekilde yeniler. İşleyiciniz her zaman kullanılabilir bir belirteç görür (veya `authFailedAt` ayarlanmıştır).
* `getConnection(id)`, tek satırlık karşılığıdır.
</Accordion>
<Accordion title="Kullanıcıya özel ve çalışma alanı paylaşımlı görünürlük" description="Kullanıcıların özel ve paylaşılan kimlik bilgileri arasında nasıl seçim yaptığı">
Bir kullanıcı "Bağlantı ekle"ye tıkladığında, bir görünürlük seçmesi istenir:
* **Yalnızca benim için** — kimlik bilgisi, bağlanan kullanıcıya özeldir. Adlarına çağrılan herhangi bir mantık işlevi (`isAuthRequired: true` ile HTTP rota tetikleyicisi) bunu görür; cron tetikleyicileri ve veritabanı olayları görmez.
* **Çalışma alanı paylaşımlı** — herhangi bir çalışma alanı üyesi bu kimlik bilgisini kullanabilir. Cron / veritabanı tetikleyicileri de görür, çünkü istek kullanıcısı yoktur.
Her işleyici için doğru olanı kullanın:
```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');
```
Kullanıcı ve sağlayıcı başına birden çok bağlantıya izin verilir; böylece aynı kullanıcı "Personal Linear" ve "Work Linear" bağlantılarını yan yana tutabilir.
</Accordion>
<Accordion title="Tek seferlik sağlayıcı kurulumu" description="OAuth uygulamanızı üçüncü taraf hizmete kaydedin">
Her bağlantı sağlayıcısı için, sunucu yöneticisinin önce üçüncü tarafta bir OAuth uygulaması kaydetmesi gerekir.
1. Sağlayıcının geliştirici ayarlarına gidin (örn. https://linear.app/settings/api/applications/new).
2. **Redirect URI**'yi `\<SERVER_URL>/auth/apps/callback` olarak ayarlayın.
3. Oluşturulan **Client ID** ve **Client Secret**'ı kopyalayın.
4. Yüklü uygulamayı Twenty'de bir sunucu yöneticisi olarak açın → karşılık gelen `serverVariables` üzerinde değerleri ayarlayın.
5. Ardından çalışma alanı üyeleri, uygulama başına **Bağlantılar** bölümünden bağlantılar ekleyebilir.
</Accordion>
</AccordionGroup>