From 6062c6eac99104563224978c8929d113ff0767f5 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Wed, 1 Jul 2026 12:35:50 +0900 Subject: [PATCH 01/10] docs(auth): switch auth connection docs from tailorctl to SDK - Rewrite the AuthConnection guide to use defineAuth() connections and tailor-sdk CLI instead of tailorctl, reflecting the in-place update behavior from sdk#1603 - Sync docs/sdk/services/auth.md from the SDK repo to pick up the same change - Update the setup-auth-connections tutorial's change-detection note to describe in-place updates instead of the old hash-based revoke/recreate behavior --- docs/guides/auth/authconnection.md | 232 ++++++++++++------ docs/sdk/services/auth.md | 18 +- .../setup-auth/setup-auth-connections.md | 4 +- 3 files changed, 162 insertions(+), 92 deletions(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 2f60c1e..d20faae 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -21,65 +21,88 @@ AuthConnection provides a secure way to: - OAuth2 application configured with your chosen provider - Basic understanding of OAuth2 flows - Function service enabled (for runtime integration) +- An SDK project with a `tailor.config.ts` — see the [SDK Quickstart](/sdk/quickstart) -## CLI Commands +## Setup Flow -The `tailorctl workspace authconnection` command manages OAuth2 connections in your workspace. +Setting up an auth connection requires two steps: -### Create an Auth Connection +1. **Configure and deploy** the connection (registers the OAuth2 provider credentials) +2. **Authorize** the connection (runs the OAuth2 flow to obtain and store tokens) -```bash -tailorctl workspace authconnection create \ - --name \ - --type TYPE_OAUTH2 \ - --provider-url \ - --issuer-url \ - --client-id \ - --client-secret +### 1. Configure the Connection + +Add a `connections` block to `defineAuth()` in `tailor.config.ts`: + +```typescript +import { defineAuth } from "@tailor-platform/sdk"; + +export const auth = defineAuth("my-auth", { + // ...other auth config + connections: { + "google-connection": { + type: "oauth2", + providerUrl: "https://accounts.google.com", + issuerUrl: "https://accounts.google.com", + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + }, + }, +}); ``` -**Parameters:** +Store the client ID and secret in your `.env` file or CI secrets — never commit them. -- `--name` (required): Unique connection name (lowercase, alphanumeric and hyphens only) -- `--type` (required): Authentication type (currently only `TYPE_OAUTH2` supported) -- `--provider-url` (required): OAuth2 provider URL -- `--issuer-url` (optional): Token issuer URL for JWT validation -- `--client-id` (required): OAuth2 client ID from the provider -- `--client-secret` (required): OAuth2 client secret from the provider +**Connection config fields:** -### List Auth Connections +| Field | Type | Required | Description | +| -------------- | -------- | -------- | --------------------------------------------- | +| `type` | `string` | Yes | Connection type. Currently only `"oauth2"`. | +| `providerUrl` | `string` | Yes | OAuth2 provider URL. | +| `issuerUrl` | `string` | Yes | Token issuer URL for JWT validation. | +| `clientId` | `string` | Yes | OAuth2 client ID from the provider. | +| `clientSecret` | `string` | Yes | OAuth2 client secret from the provider. | +| `authUrl` | `string` | No | Override for the authorization endpoint. | +| `tokenUrl` | `string` | No | Override for the token endpoint. | + +Deploy to register the connection with the platform: ```bash -tailorctl workspace authconnection list +tailor-sdk deploy ``` -### Authorize an Auth Connection +This creates (or updates) the connection record. A newly created connection exists but is not yet authorized — it has no tokens yet. + +> [!NOTE] +> Deploy updates existing connections **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), the deploy will warn you to run `authconnection authorize` again. + +### 2. Authorize the Connection ```bash -tailorctl workspace authconnection authorize \ - --scopes \ - --port +tailor-sdk authconnection authorize --name google-connection \ + --scopes "openid,profile,email" \ + --port 8080 ``` **Parameters:** -- `connection-name` (required): Name of the existing auth connection -- `--scopes` (optional): OAuth2 scopes to request (default: openid,profile,email) -- `--port` (optional): Local callback server port (default: 8080) +- `--name` (required): Name of the connection defined in `defineAuth()` +- `--scopes` (optional): OAuth2 scopes to request (default: `openid,profile,email`) +- `--port` (optional): Local callback server port (default: `8080`) - `--no-browser` (optional): Don't open browser automatically This command: -1. Starts a local HTTP server for OAuth2 callback +1. Starts a local HTTP server for the OAuth2 callback 2. Opens your browser to the provider's authorization page 3. Handles the callback after authorization -4. Exchanges the authorization code for tokens using stored client secret +4. Exchanges the authorization code for tokens using the client secret from your config 5. Stores tokens securely on the server -### Revoke an Auth Connection +Verify the connection is authorized: ```bash -tailorctl workspace authconnection revoke +tailor-sdk authconnection list ``` ## Provider Configuration Examples @@ -92,18 +115,24 @@ First, create OAuth2 credentials in [Google Cloud Console](https://console.cloud 2. Create OAuth 2.0 Client ID 3. Configure authorized redirect URIs +```typescript +connections: { + "google-oauth": { + type: "oauth2", + providerUrl: "https://accounts.google.com", + issuerUrl: "https://accounts.google.com", + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + }, +}, +``` + ```bash -# 1. Create the connection -tailorctl workspace authconnection create \ - --name google-oauth \ - --type TYPE_OAUTH2 \ - --provider-url "https://accounts.google.com" \ - --issuer-url "https://accounts.google.com" \ - --client-id "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com" \ - --client-secret "YOUR_GOOGLE_CLIENT_SECRET" +# 1. Deploy the connection +tailor-sdk deploy # 2. Authorize and get tokens -tailorctl workspace authconnection authorize google-oauth \ +tailor-sdk authconnection authorize --name google-oauth \ --scopes "https://www.googleapis.com/auth/admin.directory.user.readonly" ``` @@ -122,17 +151,24 @@ First, register an application in [Azure Portal](https://portal.azure.com/): 3. Configure redirect URIs under "Authentication" 4. Create client secret under "Certificates & secrets" +```typescript +connections: { + "ms365-oauth": { + type: "oauth2", + providerUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID", + issuerUrl: "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0", + clientId: process.env.AZURE_CLIENT_ID!, + clientSecret: process.env.AZURE_CLIENT_SECRET!, + }, +}, +``` + ```bash -tailorctl workspace authconnection create \ - --name ms365-oauth \ - --type TYPE_OAUTH2 \ - --provider-url "https://login.microsoftonline.com/YOUR_TENANT_ID" \ - --issuer-url "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0" \ - --client-id "YOUR_AZURE_APP_CLIENT_ID" \ - --client-secret "YOUR_AZURE_APP_CLIENT_SECRET" +# 1. Deploy the connection +tailor-sdk deploy # 2. Authorize and get tokens -tailorctl workspace authconnection authorize ms365-oauth \ +tailor-sdk authconnection authorize --name ms365-oauth \ --scopes "https://graph.microsoft.com/.default" ``` @@ -153,14 +189,20 @@ First, create an app in [Intuit Developer Portal](https://developer.intuit.com/) 3. Configure redirect URIs 4. Get your client ID and secret from "Keys & OAuth" +```typescript +connections: { + "quickbooks-oauth": { + type: "oauth2", + providerUrl: "https://appcenter.intuit.com/connect/oauth2", + issuerUrl: "https://oauth.platform.intuit.com/op/v1", + clientId: process.env.QUICKBOOKS_CLIENT_ID!, + clientSecret: process.env.QUICKBOOKS_CLIENT_SECRET!, + }, +}, +``` + ```bash -tailorctl workspace authconnection create \ - --name quickbooks-oauth \ - --type TYPE_OAUTH2 \ - --provider-url "https://appcenter.intuit.com/connect/oauth2" \ - --issuer-url "https://oauth.platform.intuit.com/op/v1" \ - --client-id "YOUR_QUICKBOOKS_CLIENT_ID" \ - --client-secret "YOUR_QUICKBOOKS_CLIENT_SECRET" +tailor-sdk deploy ``` **Common QuickBooks OAuth2 URLs:** @@ -183,10 +225,14 @@ AuthConnection integrates seamlessly with the Function service, allowing your fu ### Basic Usage -```javascript +Use `auth.getConnectionToken()` — imported from `tailor.config.ts` — to retrieve the current access token. When `connections` is defined in `defineAuth()`, the connection name is type-checked and autocompleted against the defined keys: + +```typescript +import { auth } from "../tailor.config"; + export default async () => { // Get access token for a connection - const tokens = await tailor.authconnection.getConnectionToken("google-oauth"); + const tokens = await auth.getConnectionToken("google-oauth"); // Use the access token to call external APIs const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", { @@ -204,14 +250,20 @@ export default async () => { }; ``` +```typescript +// auth.getConnectionToken("unknown-connection"); // ❌ TypeScript error +``` + ### Advanced Usage with Error Handling -```javascript +```typescript +import { auth } from "../tailor.config"; + export default async () => { try { // Get tokens for multiple connections - const googleTokens = await tailor.authconnection.getConnectionToken("google-oauth"); - const msTokens = await tailor.authconnection.getConnectionToken("ms365-oauth"); + const googleTokens = await auth.getConnectionToken("google-oauth"); + const msTokens = await auth.getConnectionToken("ms365-oauth"); // Call Google API const googleResponse = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", { @@ -260,6 +312,26 @@ The `getConnectionToken()` method returns an object with the following propertie Token type (typically "Bearer") +## Managing Connections via CLI + +Connections created outside `defineAuth()` — or connections you'd rather not check into config — can be managed entirely via the CLI: + +```bash +# Open the connections page in the Console (recommended for creating connections/tokens) +tailor-sdk authconnection open + +# Authorize (opens browser for OAuth2 flow) +tailor-sdk authconnection authorize --name + +# List all connections +tailor-sdk authconnection list + +# Revoke a connection +tailor-sdk authconnection revoke --name +``` + +When `connections` is not defined in `defineAuth()`, `auth.getConnectionToken()` accepts any string, so you can still read tokens for CLI-managed connections at runtime — you just lose the type-checked autocompletion. + ## Best Practices ### Security Considerations @@ -281,22 +353,23 @@ Use descriptive names that indicate the provider and environment: For development vs production environments, create separate connections: -```bash -# Development -tailorctl workspace authconnection create \ - --name google-oauth-dev \ - --type TYPE_OAUTH2 \ - --provider-url "https://accounts.google.com" \ - --client-id "DEV_CLIENT_ID.apps.googleusercontent.com" \ - --client-secret "DEV_CLIENT_SECRET" - -# Production -tailorctl workspace authconnection create \ - --name google-oauth-prod \ - --type TYPE_OAUTH2 \ - --provider-url "https://accounts.google.com" \ - --client-id "PROD_CLIENT_ID.apps.googleusercontent.com" \ - --client-secret "PROD_CLIENT_SECRET" +```typescript +connections: { + "google-oauth-dev": { + type: "oauth2", + providerUrl: "https://accounts.google.com", + issuerUrl: "https://accounts.google.com", + clientId: process.env.GOOGLE_CLIENT_ID_DEV!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET_DEV!, + }, + "google-oauth-prod": { + type: "oauth2", + providerUrl: "https://accounts.google.com", + issuerUrl: "https://accounts.google.com", + clientId: process.env.GOOGLE_CLIENT_ID_PROD!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET_PROD!, + }, +}, ``` ## Troubleshooting @@ -322,6 +395,11 @@ tailorctl workspace authconnection create \ - Implement refresh token flow in your application - Monitor token expiration times +**Re-authorization Required After Deploy** + +- If `tailor-sdk deploy` warns that a connection needs re-authorization, run `tailor-sdk authconnection authorize --name ` again +- This happens when identity-changing fields (`providerUrl`, `issuerUrl`, `clientId`, `type`) are modified + **Function Runtime Errors** - Ensure the connection name exists and is authorized @@ -330,6 +408,8 @@ tailorctl workspace authconnection create \ ## Next Steps +- [Setting up Auth Connections tutorial](/tutorials/setup-auth/setup-auth-connections) - Step-by-step SDK walkthrough +- [Auth Service SDK reference](/sdk/services/auth#auth-connections) - Full auth connections API reference - [Learn about Function service](/guides/function/overview) - Understand Function runtime capabilities - [Secret Manager documentation](/guides/secretmanager) - Secure secret storage patterns - [Auth service overview](/guides/auth/overview) - Complete authentication system diff --git a/docs/sdk/services/auth.md b/docs/sdk/services/auth.md index 1c91cd8..60a6e80 100644 --- a/docs/sdk/services/auth.md +++ b/docs/sdk/services/auth.md @@ -369,17 +369,14 @@ Auth connections enable OAuth2 authentication with external providers (Google, M For the official Tailor Platform documentation, see [AuthConnection Guide](/guides/auth/authconnection). -> [!WARNING] -> **Managing connections through `tailor.config.ts` is unreliable for shared and CI deploys.** -> A deploy revokes and recreates the connection — discarding the token obtained via `authconnection authorize` — whenever it cannot confirm the secret is unchanged. That check relies on a hash stored locally in `.tailor-sdk/secrets-state.json`, which is gitignored and therefore not shared across machines. So a deploy from CI, a clean checkout, another developer's machine, or after deleting `.tailor-sdk/` recreates the connection and drops its token. Only repeated deploys from the same machine that still holds that state keep the token. -> -> Because of this, prefer to **create the connection and its token from the Console**. You can jump to the connections page with: +> [!NOTE] +> Deploy updates connections **in-place**, preserving the OAuth token. If the connection requires re-authorization after an update, the deploy will warn you: > > ```bash +> tailor-sdk authconnection authorize --name +> # Or via the Console: > tailor-sdk authconnection open > ``` -> -> The `connections` field in `defineAuth()` and the `authconnection authorize` flow are documented below for reference. ### Setup Flow @@ -432,13 +429,6 @@ The authorize command opens a browser for the OAuth2 flow. The authorization cod | `authUrl` | `string` | No | Override for the authorization endpoint. | | `tokenUrl` | `string` | No | Override for the token endpoint. | -### Change Detection - -The SDK uses hash-based change detection for connection configs. Only connections whose configuration has changed since the last `apply` are updated (revoked and recreated). Deleting the `.tailor-sdk/` directory forces all connections to be re-sent. - -> [!WARNING] -> The secret hash lives in `.tailor-sdk/secrets-state.json`, which is gitignored and not shared across machines or CI. When that state is missing — a clean checkout, CI, another machine, or after deleting `.tailor-sdk/` — a deploy cannot confirm the secret is unchanged, so it revokes and recreates the connection and discards the token stored by `authconnection authorize`. For shared and CI workflows, manage the connection and create its token from the Console (`tailor-sdk authconnection open`) instead. - ### `auth.getConnectionToken()` `auth.getConnectionToken()` retrieves connection tokens at runtime by calling `tailor.authconnection.getConnectionToken()` internally. When `connections` is defined in `defineAuth()`, the connection name is type-checked and autocompleted against the defined keys: diff --git a/docs/tutorials/setup-auth/setup-auth-connections.md b/docs/tutorials/setup-auth/setup-auth-connections.md index d31f67a..0100bfb 100644 --- a/docs/tutorials/setup-auth/setup-auth-connections.md +++ b/docs/tutorials/setup-auth/setup-auth-connections.md @@ -66,8 +66,8 @@ tailor-sdk apply This creates the connection record. The connection exists but is not yet authorized (it has no tokens yet). -::: info Hash-based change detection -The SDK only re-deploys connections whose config has changed since the last `apply`. Delete `.tailor-sdk/` to force all connections to re-sync. +::: info In-place updates +Redeploying updates an existing connection **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), `apply` will warn you to run `authconnection authorize` again. ::: ### 3. Authorize the Connection From da5f5dda00bd2fc0e2c74bf95b472d2c2f92905f Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Wed, 1 Jul 2026 13:04:53 +0900 Subject: [PATCH 02/10] docs(auth): mention authconnection open as an alternative to authorize --- docs/guides/auth/authconnection.md | 6 ++++++ docs/tutorials/setup-auth/setup-auth-connections.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index d20faae..6c49ab7 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -99,6 +99,12 @@ This command: 4. Exchanges the authorization code for tokens using the client secret from your config 5. Stores tokens securely on the server +Alternatively, run `tailor-sdk authconnection open` to open the connection's page in the Console and authorize it from there — useful when you'd rather not run the CLI flow locally (for example, on a machine without browser access). + +```bash +tailor-sdk authconnection open +``` + Verify the connection is authorized: ```bash diff --git a/docs/tutorials/setup-auth/setup-auth-connections.md b/docs/tutorials/setup-auth/setup-auth-connections.md index 0100bfb..7d65b2d 100644 --- a/docs/tutorials/setup-auth/setup-auth-connections.md +++ b/docs/tutorials/setup-auth/setup-auth-connections.md @@ -81,6 +81,12 @@ tailor-sdk authconnection authorize --name google-connection \ This opens a browser tab for the OAuth2 consent screen. After you approve, the platform exchanges the authorization code for tokens and stores them securely. Your app code never handles the tokens directly. +You can also run `tailor-sdk authconnection open` to authorize from the Console instead of the local CLI flow: + +```bash +tailor-sdk authconnection open +``` + Verify the connection is authorized: ```bash From 6efeeb345e877ac38c119ec7af50bb7f3cfc979e Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Wed, 1 Jul 2026 13:15:32 +0900 Subject: [PATCH 03/10] docs(auth): use per-environment .env files instead of suffixed env vars --- docs/guides/auth/authconnection.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 6c49ab7..8831fb6 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -357,27 +357,24 @@ Use descriptive names that indicate the provider and environment: ### Environment-Specific Configurations -For development vs production environments, create separate connections: +Since each environment is deployed to its own workspace from the same `tailor.config.ts`, keep a single connection definition and vary the value via a per-environment `.env` file — the same pattern used for any other environment-specific value (see [Multi-Environment Configuration](/sdk/multi-environment)): ```typescript connections: { - "google-oauth-dev": { - type: "oauth2", - providerUrl: "https://accounts.google.com", - issuerUrl: "https://accounts.google.com", - clientId: process.env.GOOGLE_CLIENT_ID_DEV!, - clientSecret: process.env.GOOGLE_CLIENT_SECRET_DEV!, - }, - "google-oauth-prod": { + "google-oauth": { type: "oauth2", providerUrl: "https://accounts.google.com", issuerUrl: "https://accounts.google.com", - clientId: process.env.GOOGLE_CLIENT_ID_PROD!, - clientSecret: process.env.GOOGLE_CLIENT_SECRET_PROD!, + clientId: process.env.GOOGLE_CLIENT_ID!, + clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, }, ``` +```bash +tailor-sdk deploy -w --env-file .env.production +``` + ## Troubleshooting ### Common Issues From 561ad096290712a3dc745e19d0cd284b1f7c5dce Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Wed, 1 Jul 2026 14:07:01 +0900 Subject: [PATCH 04/10] docs(auth): use @tailor-platform/sdk/runtime authconnection at runtime Switch runtime token examples from auth.getConnectionToken() (the defineAuth() return value, which requires importing tailor.config.ts into runtime code) to authconnection.getConnectionToken() from @tailor-platform/sdk/runtime. --- docs/guides/auth/authconnection.md | 19 ++++++++----------- .../setup-auth/setup-auth-connections.md | 16 +++++----------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 8831fb6..be350a1 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -231,14 +231,14 @@ AuthConnection integrates seamlessly with the Function service, allowing your fu ### Basic Usage -Use `auth.getConnectionToken()` — imported from `tailor.config.ts` — to retrieve the current access token. When `connections` is defined in `defineAuth()`, the connection name is type-checked and autocompleted against the defined keys: +Use `authconnection.getConnectionToken()` from `@tailor-platform/sdk/runtime` to retrieve the current access token by connection name: ```typescript -import { auth } from "../tailor.config"; +import { authconnection } from "@tailor-platform/sdk/runtime"; export default async () => { // Get access token for a connection - const tokens = await auth.getConnectionToken("google-oauth"); + const tokens = await authconnection.getConnectionToken("google-oauth"); // Use the access token to call external APIs const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", { @@ -256,20 +256,16 @@ export default async () => { }; ``` -```typescript -// auth.getConnectionToken("unknown-connection"); // ❌ TypeScript error -``` - ### Advanced Usage with Error Handling ```typescript -import { auth } from "../tailor.config"; +import { authconnection } from "@tailor-platform/sdk/runtime"; export default async () => { try { // Get tokens for multiple connections - const googleTokens = await auth.getConnectionToken("google-oauth"); - const msTokens = await auth.getConnectionToken("ms365-oauth"); + const googleTokens = await authconnection.getConnectionToken("google-oauth"); + const msTokens = await authconnection.getConnectionToken("ms365-oauth"); // Call Google API const googleResponse = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", { @@ -336,7 +332,7 @@ tailor-sdk authconnection list tailor-sdk authconnection revoke --name ``` -When `connections` is not defined in `defineAuth()`, `auth.getConnectionToken()` accepts any string, so you can still read tokens for CLI-managed connections at runtime — you just lose the type-checked autocompletion. +`authconnection.getConnectionToken()` takes a plain connection name string, so it works the same way for connections managed entirely via the CLI as for connections defined in `defineAuth()`. ## Best Practices @@ -413,6 +409,7 @@ tailor-sdk deploy -w --env-file .env.production - [Setting up Auth Connections tutorial](/tutorials/setup-auth/setup-auth-connections) - Step-by-step SDK walkthrough - [Auth Service SDK reference](/sdk/services/auth#auth-connections) - Full auth connections API reference +- [Runtime API](/sdk/runtime#namespaces) - `@tailor-platform/sdk/runtime` namespaces, including `authconnection` - [Learn about Function service](/guides/function/overview) - Understand Function runtime capabilities - [Secret Manager documentation](/guides/secretmanager) - Secure secret storage patterns - [Auth service overview](/guides/auth/overview) - Complete authentication system diff --git a/docs/tutorials/setup-auth/setup-auth-connections.md b/docs/tutorials/setup-auth/setup-auth-connections.md index 7d65b2d..928c0ae 100644 --- a/docs/tutorials/setup-auth/setup-auth-connections.md +++ b/docs/tutorials/setup-auth/setup-auth-connections.md @@ -95,12 +95,12 @@ tailor-sdk authconnection list ### 4. Use the Connection Token at Runtime -Use `auth.getConnectionToken()` in a resolver, executor, or workflow to retrieve the current access token: +Use `authconnection.getConnectionToken()` from `@tailor-platform/sdk/runtime` in a resolver, executor, or workflow to retrieve the current access token: ```typescript // resolvers/fetch-google-profile.ts import { createResolver, t } from "@tailor-platform/sdk"; -import { auth } from "../tailor.config"; +import { authconnection } from "@tailor-platform/sdk/runtime"; export default createResolver({ name: "fetchGoogleProfile", @@ -111,7 +111,7 @@ export default createResolver({ name: t.string(), }), body: async () => { - const tokens = await auth.getConnectionToken("google-connection"); + const tokens = await authconnection.getConnectionToken("google-connection"); const response = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", { headers: { @@ -125,12 +125,6 @@ export default createResolver({ }); ``` -The connection name is **type-checked**. If you rename or remove a connection from `defineAuth()`, TypeScript will flag any call sites immediately. - -```typescript -// auth.getConnectionToken("unknown-connection"); // ❌ TypeScript error -``` - ### 5. Manage Connections via CLI ```bash @@ -152,7 +146,7 @@ Here's a full executor that calls the Google Calendar API whenever a meeting rec ```typescript // executor/sync-to-google-calendar.ts import { createExecutor, recordCreatedTrigger } from "@tailor-platform/sdk"; -import { auth } from "../tailor.config"; +import { authconnection } from "@tailor-platform/sdk/runtime"; import { meeting } from "../db/meeting"; export default createExecutor({ @@ -162,7 +156,7 @@ export default createExecutor({ operation: { kind: "function", body: async ({ newRecord }) => { - const tokens = await auth.getConnectionToken("google-connection"); + const tokens = await authconnection.getConnectionToken("google-connection"); await fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", { method: "POST", From b943a0a3a45a40d2be15ff9b53a392dbd5f0ee6a Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Wed, 1 Jul 2026 21:54:33 +0900 Subject: [PATCH 05/10] docs(auth): reflect ConnectionNameRegistry type narrowing from sdk#1605 - Sync docs/sdk/services/auth.md and docs/sdk/testing.md from the SDK repo - authconnection.getConnectionToken() from @tailor-platform/sdk/runtime is now type-checked against defineAuth()'s connections via a generated ConnectionNameRegistry (mirroring MachineUserNameRegistry); document the narrowing and the tailor-sdk generate/deploy step needed to refresh it - Fix Token Properties to list only access_token, matching the corrected AuthConnectionTokenResult shape (GetConnectionSessionToken never returns refresh_token, token_type, or expires_at) - Fix stray tailor-sdk apply references to deploy, the actual command name --- docs/guides/auth/authconnection.md | 24 +++++++------------ docs/sdk/services/auth.md | 18 +++++++------- docs/sdk/testing.md | 2 +- .../setup-auth/setup-auth-connections.md | 14 ++++++++--- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index be350a1..8b33724 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -231,7 +231,7 @@ AuthConnection integrates seamlessly with the Function service, allowing your fu ### Basic Usage -Use `authconnection.getConnectionToken()` from `@tailor-platform/sdk/runtime` to retrieve the current access token by connection name: +Use `authconnection.getConnectionToken()` from `@tailor-platform/sdk/runtime` to retrieve the current access token by connection name. When `connections` is defined in `defineAuth()`, the connection name is type-checked and autocompleted against the defined keys: ```typescript import { authconnection } from "@tailor-platform/sdk/runtime"; @@ -256,6 +256,12 @@ export default async () => { }; ``` +```typescript +// authconnection.getConnectionToken("unknown-connection"); // ❌ TypeScript error +``` + +Type narrowing is provided by the generated `tailor.d.ts` (the `ConnectionNameRegistry` interface). Run `tailor-sdk generate` (or `deploy`) after defining new connections to refresh it. Before the first generate, or when `connections` is not defined in `defineAuth()`, `getConnectionToken()` accepts any string. + ### Advanced Usage with Error Handling ```typescript @@ -296,24 +302,12 @@ export default async () => { ### Token Properties -The `getConnectionToken()` method returns an object with the following properties: +The `getConnectionToken()` method returns an object with the following property: The OAuth2 access token for API calls - - The refresh token (if available from provider) - - - - Token expiration timestamp in ISO format - - - - Token type (typically "Bearer") - - ## Managing Connections via CLI Connections created outside `defineAuth()` — or connections you'd rather not check into config — can be managed entirely via the CLI: @@ -332,7 +326,7 @@ tailor-sdk authconnection list tailor-sdk authconnection revoke --name ``` -`authconnection.getConnectionToken()` takes a plain connection name string, so it works the same way for connections managed entirely via the CLI as for connections defined in `defineAuth()`. +For connections not defined in `defineAuth()`'s `connections`, `authconnection.getConnectionToken()` still accepts any string, so you can read tokens for CLI-managed connections the same way — you just lose the type-checked autocompletion. ## Best Practices diff --git a/docs/sdk/services/auth.md b/docs/sdk/services/auth.md index 60a6e80..43391f2 100644 --- a/docs/sdk/services/auth.md +++ b/docs/sdk/services/auth.md @@ -308,7 +308,7 @@ export default createResolver({ }); ``` -Type narrowing is provided by the generated `tailor.d.ts` (the `MachineUserNameRegistry` interface). Run `tailor-sdk generate` (or `apply`) after defining new machine users to refresh it. +Type narrowing is provided by the generated `tailor.d.ts` (the `MachineUserNameRegistry` interface). Run `tailor-sdk generate` (or `deploy`) after defining new machine users to refresh it. > **Deprecated:** The `auth.invoker("")` helper is still available for backward compatibility. Prefer the string form — it does not require importing `auth` from `tailor.config.ts` into runtime files, avoiding bundling config-layer (Node-only) dependencies. @@ -365,7 +365,7 @@ See [IdP](idp) for configuring identity providers. ## Auth Connections -Auth connections enable OAuth2 authentication with external providers (Google, Microsoft 365, QuickBooks, etc.) for application-to-application flows. Functions can access connection tokens at runtime via `tailor.authconnection.getConnectionToken()`. +Auth connections enable OAuth2 authentication with external providers (Google, Microsoft 365, QuickBooks, etc.) for application-to-application flows. Functions can access connection tokens at runtime via `authconnection.getConnectionToken()` (from `@tailor-platform/sdk/runtime`). For the official Tailor Platform documentation, see [AuthConnection Guide](/guides/auth/authconnection). @@ -429,23 +429,25 @@ The authorize command opens a browser for the OAuth2 flow. The authorization cod | `authUrl` | `string` | No | Override for the authorization endpoint. | | `tokenUrl` | `string` | No | Override for the token endpoint. | -### `auth.getConnectionToken()` +### Retrieving Connection Tokens -`auth.getConnectionToken()` retrieves connection tokens at runtime by calling `tailor.authconnection.getConnectionToken()` internally. When `connections` is defined in `defineAuth()`, the connection name is type-checked and autocompleted against the defined keys: +Use `authconnection.getConnectionToken()` from `@tailor-platform/sdk/runtime` to retrieve connection tokens at runtime. The connection name is type-checked and autocompleted against the connections defined in `defineAuth()`'s `connections`: ```typescript -import { auth } from "../tailor.config"; +import { authconnection } from "@tailor-platform/sdk/runtime"; // In a resolver, executor, or workflow: -const tokens = await auth.getConnectionToken("google-connection"); +const tokens = await authconnection.getConnectionToken("google-connection"); const response = await fetch("https://www.googleapis.com/...", { headers: { Authorization: `Bearer ${tokens.access_token}` }, }); -// auth.getConnectionToken("unknown"); // Type error — only "google-connection" is allowed +// authconnection.getConnectionToken("unknown"); // Type error — only "google-connection" is allowed ``` -When `connections` is not defined, `getConnectionToken()` accepts any string. This supports connections managed entirely via the CLI. +Type narrowing is provided by the generated `tailor.d.ts` (the `ConnectionNameRegistry` interface). Run `tailor-sdk generate` (or `deploy`) after defining new connections to refresh it. Before the first generate, or when `connections` is not defined in `defineAuth()`, `getConnectionToken()` accepts any string — this also supports connections managed entirely via the CLI. + +> **Deprecated:** `auth.getConnectionToken("")` still works, but is deprecated. Importing `auth` from `tailor.config.ts` into runtime files pulls config-layer (Node-only) dependencies into the bundle. See [Built-in Interfaces](/guides/function/builtin-interfaces.html#auth-connection) for the full runtime API. diff --git a/docs/sdk/testing.md b/docs/sdk/testing.md index b3e8ebb..7e918b2 100644 --- a/docs/sdk/testing.md +++ b/docs/sdk/testing.md @@ -185,7 +185,7 @@ import { mockAuthconnection } from "@tailor-platform/sdk/vitest"; test("returns configured token", async () => { using ac = mockAuthconnection(); ac.setTokens({ - google: { access_token: "ya29.xxx", expires_in: 3600 }, + google: { access_token: "ya29.xxx" }, }); const token = await tailor.authconnection.getConnectionToken("google"); diff --git a/docs/tutorials/setup-auth/setup-auth-connections.md b/docs/tutorials/setup-auth/setup-auth-connections.md index 928c0ae..516c84e 100644 --- a/docs/tutorials/setup-auth/setup-auth-connections.md +++ b/docs/tutorials/setup-auth/setup-auth-connections.md @@ -58,16 +58,16 @@ Store `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` in your `.env` file or CI se ### 2. Deploy the Connection -Run `tailor-sdk apply` to register the connection with the platform: +Run `tailor-sdk deploy` to register the connection with the platform: ```bash -tailor-sdk apply +tailor-sdk deploy ``` This creates the connection record. The connection exists but is not yet authorized (it has no tokens yet). ::: info In-place updates -Redeploying updates an existing connection **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), `apply` will warn you to run `authconnection authorize` again. +Redeploying updates an existing connection **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), `deploy` will warn you to run `authconnection authorize` again. ::: ### 3. Authorize the Connection @@ -125,6 +125,14 @@ export default createResolver({ }); ``` +The connection name is **type-checked** against the connections defined in `defineAuth()`. If you rename or remove a connection, TypeScript will flag any call sites immediately. + +```typescript +// authconnection.getConnectionToken("unknown-connection"); // ❌ TypeScript error +``` + +Type narrowing comes from the generated `tailor.d.ts`. Run `tailor-sdk generate` (or `deploy`) after adding or renaming connections to refresh it. + ### 5. Manage Connections via CLI ```bash From a3fb5457d299618e8927a6c126b6c0a082825fc3 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Thu, 2 Jul 2026 22:08:26 +0900 Subject: [PATCH 06/10] docs(auth): document Console-only setup and Console/SDK-config exclusivity - Add Option B: fully managing a connection (create, authorize, revoke, delete) from the Console via 'tailor-sdk authconnection open', with no tailor.config.ts changes - Add a Choosing a Management Method section explaining that a connection is owned by exactly one side at a time, based on the SDK's ownership-label mechanism (planAuthConnections/confirmUnmanagedResources in the SDK source): a Console-created connection has no label and deploy leaves it alone, but declaring the same name in defineAuth() prompts to adopt it into config management, after which config always wins - Add the 'authconnection delete' command (removes the connection entirely, vs 'revoke' which only invalidates tokens) and a note not to delete a config-managed connection out of band - Point the setup-auth-connections tutorial at the guide's Setup Flow section for readers who want the Console-only path instead --- docs/guides/auth/authconnection.md | 53 +++++++++++++++---- .../setup-auth/setup-auth-connections.md | 2 + 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 8b33724..640aa64 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -25,12 +25,16 @@ AuthConnection provides a secure way to: ## Setup Flow -Setting up an auth connection requires two steps: +An auth connection can be managed two ways: through your **SDK config** (`defineAuth()` in `tailor.config.ts`, deployed with the rest of your app) or entirely through the **Console**. Either way, setting up a connection is two steps: -1. **Configure and deploy** the connection (registers the OAuth2 provider credentials) +1. **Create** the connection (registers the OAuth2 provider credentials) 2. **Authorize** the connection (runs the OAuth2 flow to obtain and store tokens) -### 1. Configure the Connection +A given connection can only be managed by one side at a time — see [Choosing a Management Method](#choosing-a-management-method) below. + +### Option A: Manage via SDK Config + +#### 1. Configure the Connection Add a `connections` block to `defineAuth()` in `tailor.config.ts`: @@ -76,7 +80,7 @@ This creates (or updates) the connection record. A newly created connection exis > [!NOTE] > Deploy updates existing connections **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), the deploy will warn you to run `authconnection authorize` again. -### 2. Authorize the Connection +#### 2. Authorize the Connection ```bash tailor-sdk authconnection authorize --name google-connection \ @@ -99,7 +103,7 @@ This command: 4. Exchanges the authorization code for tokens using the client secret from your config 5. Stores tokens securely on the server -Alternatively, run `tailor-sdk authconnection open` to open the connection's page in the Console and authorize it from there — useful when you'd rather not run the CLI flow locally (for example, on a machine without browser access). +Alternatively, run `tailor-sdk authconnection open` to authorize the connection from the Console instead of the local CLI flow — useful on a machine without browser access: ```bash tailor-sdk authconnection open @@ -111,6 +115,32 @@ Verify the connection is authorized: tailor-sdk authconnection list ``` +### Option B: Manage Entirely via the Console + +No `tailor.config.ts` changes are needed — create, authorize, and manage the connection directly in the Tailor Platform Console: + +```bash +tailor-sdk authconnection open +``` + +This opens the workspace's connections settings page, where you can: + +1. **Create** a new connection — enter the type, provider URL, issuer URL, client ID, and client secret directly in the Console form +2. **Authorize** it — the Console walks you through the OAuth2 consent flow in the browser +3. View its status, re-authorize, revoke its tokens, or delete it later from the same page + +This is a good fit for connections you don't want tracked in git — for example, ones that differ per developer, or that only ever target a single shared workspace and don't need to travel with the rest of your app's config. + +### Choosing a Management Method + +A connection name is owned by exactly one side at a time — the same connection cannot be managed by both the Console and your SDK config simultaneously: + +- A connection created via the Console (Option B) carries no SDK ownership label. `tailor-sdk deploy` leaves it completely untouched as long as it isn't also declared in `connections`. +- If you later add a connection with the **same name** to `defineAuth()`'s `connections` and run `deploy`, the SDK finds it already exists without an ownership label and pauses to ask whether it should take it over ("Allow tailor-sdk to manage these resources?"). Confirming adopts it into config management — from that point on, every `deploy` overwrites its fields to match your config, and removing it from `connections` deletes the connection. (`--yes` confirms automatically, which is useful for CI but means this adoption happens without a prompt.) +- Until you confirm that handover, `deploy` refuses to proceed, so a Console-managed connection is never silently overwritten by config just because a connection with the same name appears in `tailor.config.ts`. + +In short: use the Console (Option B) for connections you intend to manage by hand, and SDK config (Option A) for connections you want defined, reviewed, and deployed alongside the rest of your app. Don't mix the two for the same connection name. + ## Provider Configuration Examples ### Google OAuth2 @@ -310,10 +340,10 @@ The `getConnectionToken()` method returns an object with the following property: ## Managing Connections via CLI -Connections created outside `defineAuth()` — or connections you'd rather not check into config — can be managed entirely via the CLI: +These commands work on any connection regardless of which option created it: ```bash -# Open the connections page in the Console (recommended for creating connections/tokens) +# Open the connections page in the Console tailor-sdk authconnection open # Authorize (opens browser for OAuth2 flow) @@ -322,11 +352,16 @@ tailor-sdk authconnection authorize --name # List all connections tailor-sdk authconnection list -# Revoke a connection +# Revoke a connection's tokens (keeps the connection; re-authorize later) tailor-sdk authconnection revoke --name + +# Delete a connection entirely +tailor-sdk authconnection delete --name ``` -For connections not defined in `defineAuth()`'s `connections`, `authconnection.getConnectionToken()` still accepts any string, so you can read tokens for CLI-managed connections the same way — you just lose the type-checked autocompletion. +If a connection is declared in `defineAuth()`'s `connections` (Option A), don't `delete` it via the CLI or Console — remove it from `connections` and redeploy instead, otherwise the next `deploy` just recreates it from your config. + +For connections not defined in `defineAuth()`'s `connections`, `authconnection.getConnectionToken()` still accepts any string, so you can read tokens for Console-managed connections the same way — you just lose the type-checked autocompletion. ## Best Practices diff --git a/docs/tutorials/setup-auth/setup-auth-connections.md b/docs/tutorials/setup-auth/setup-auth-connections.md index 516c84e..1926d00 100644 --- a/docs/tutorials/setup-auth/setup-auth-connections.md +++ b/docs/tutorials/setup-auth/setup-auth-connections.md @@ -4,6 +4,8 @@ Auth connections enable your application to authenticate with external OAuth2 pr - To follow along, first complete the [SDK Quickstart](../../sdk/quickstart) and [Setting up Auth](overview). +This tutorial manages the connection through SDK config (`defineAuth()`), which is one of two supported approaches. You can instead create, authorize, and manage a connection entirely from the Console with `tailor-sdk authconnection open` — see [Setup Flow](/guides/auth/authconnection#setup-flow) in the AuthConnection guide for both options and why a given connection should only be managed by one of them. + ## What you'll build A connection to Google's API that your resolvers and functions can use to call Google services without managing OAuth2 tokens manually. From c82922d3d0e62dd8b4a13672501f6391e470a055 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Thu, 2 Jul 2026 22:36:02 +0900 Subject: [PATCH 07/10] docs(auth): simplify the delete caveat to the one-method-per-connection rule --- docs/guides/auth/authconnection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 640aa64..9581ad9 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -359,7 +359,7 @@ tailor-sdk authconnection revoke --name tailor-sdk authconnection delete --name ``` -If a connection is declared in `defineAuth()`'s `connections` (Option A), don't `delete` it via the CLI or Console — remove it from `connections` and redeploy instead, otherwise the next `deploy` just recreates it from your config. +To delete a connection managed via SDK config (Option A), remove it from `connections` and redeploy instead of using `authconnection delete` directly — manage each connection through one method only, as described above. For connections not defined in `defineAuth()`'s `connections`, `authconnection.getConnectionToken()` still accepts any string, so you can read tokens for Console-managed connections the same way — you just lose the type-checked autocompletion. From c86b6ba02c0897dd8e7947bbaa5f005387a5541c Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Thu, 2 Jul 2026 22:42:40 +0900 Subject: [PATCH 08/10] docs(auth): document empty clientSecret for CI redeploys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against the SDK source (auth-connection.ts buildUpdateMask and the auth-connection.test.ts CI-scenario tests): once a connection is already deployed, an empty clientSecret is excluded from the update mask entirely, leaving the stored secret untouched. This only applies to updates of an existing connection — the initial create still requires the real secret. Also note that the field must be an explicit empty string, not undefined, since the config schema requires a string. --- docs/guides/auth/authconnection.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 9581ad9..b59ba98 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -80,6 +80,9 @@ This creates (or updates) the connection record. A newly created connection exis > [!NOTE] > Deploy updates existing connections **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), the deploy will warn you to run `authconnection authorize` again. +> [!TIP] +> Once a connection has been deployed at least once, later deploys can pass `clientSecret: ""` (an empty string) without erasing the secret already stored on the platform — an empty value is simply left out of the update, so nothing about the secret changes. This lets a CI pipeline redeploy an existing connection (for example, to pick up a `providerUrl` change) without ever having access to the real secret. The very first deploy that creates the connection still needs the real secret at least once, from wherever that value is available. Set the environment variable to an explicit empty string (e.g. `GOOGLE_CLIENT_SECRET=` in CI) rather than leaving it unset — `clientSecret` is a required field, so an `undefined` value fails config validation before deploy even runs. + #### 2. Authorize the Connection ```bash From 8fcc14ef0fed3fa54656049f933f625ed263b5ab Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Thu, 2 Jul 2026 22:54:08 +0900 Subject: [PATCH 09/10] docs(auth): show ?? "" pattern alongside the non-null assertion pitfall process.env.X! only silences TypeScript; if the var is actually unset at runtime, clientSecret stays undefined and AuthConfigSchema.parse still throws. process.env.X ?? "" is the pattern that actually produces a string in that case. --- docs/guides/auth/authconnection.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index b59ba98..29f8ea2 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -81,7 +81,16 @@ This creates (or updates) the connection record. A newly created connection exis > Deploy updates existing connections **in-place**, preserving the OAuth token. If a change requires re-authorization (for example, the provider URL or client ID changed), the deploy will warn you to run `authconnection authorize` again. > [!TIP] -> Once a connection has been deployed at least once, later deploys can pass `clientSecret: ""` (an empty string) without erasing the secret already stored on the platform — an empty value is simply left out of the update, so nothing about the secret changes. This lets a CI pipeline redeploy an existing connection (for example, to pick up a `providerUrl` change) without ever having access to the real secret. The very first deploy that creates the connection still needs the real secret at least once, from wherever that value is available. Set the environment variable to an explicit empty string (e.g. `GOOGLE_CLIENT_SECRET=` in CI) rather than leaving it unset — `clientSecret` is a required field, so an `undefined` value fails config validation before deploy even runs. +> Once a connection has been deployed at least once, later deploys can pass `clientSecret: ""` (an empty string) without erasing the secret already stored on the platform — an empty value is simply left out of the update, so nothing about the secret changes. This lets a CI pipeline redeploy an existing connection (for example, to pick up a `providerUrl` change) without ever having access to the real secret. The very first deploy that creates the connection still needs the real secret at least once, from wherever that value is available. +> +> `clientSecret` is a required field, so it must resolve to an actual string — `undefined` fails config validation before deploy even runs, regardless of how you've typed it: +> +> ```typescript +> clientSecret: process.env.GOOGLE_CLIENT_SECRET!, // ❌ throws at deploy time if the var is unset — `!` only silences TypeScript, it doesn't change the runtime value +> clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "", // ✅ falls back to "" when the var is unset, e.g. in CI +> ``` +> +> With the `.env` file itself, an explicit empty assignment (`GOOGLE_CLIENT_SECRET=`) also works, since that sets the variable to `""` rather than leaving it unset. #### 2. Authorize the Connection From 7257b1d180f1dee90cd29b31bf64f4c406601e15 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Thu, 2 Jul 2026 23:38:54 +0900 Subject: [PATCH 10/10] docs(auth): drop the non-null assertion pitfall example, keep only ?? "" --- docs/guides/auth/authconnection.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/guides/auth/authconnection.md b/docs/guides/auth/authconnection.md index 29f8ea2..a0c0605 100644 --- a/docs/guides/auth/authconnection.md +++ b/docs/guides/auth/authconnection.md @@ -83,11 +83,10 @@ This creates (or updates) the connection record. A newly created connection exis > [!TIP] > Once a connection has been deployed at least once, later deploys can pass `clientSecret: ""` (an empty string) without erasing the secret already stored on the platform — an empty value is simply left out of the update, so nothing about the secret changes. This lets a CI pipeline redeploy an existing connection (for example, to pick up a `providerUrl` change) without ever having access to the real secret. The very first deploy that creates the connection still needs the real secret at least once, from wherever that value is available. > -> `clientSecret` is a required field, so it must resolve to an actual string — `undefined` fails config validation before deploy even runs, regardless of how you've typed it: +> `clientSecret` is a required field, so it must resolve to an actual string — `undefined` fails config validation before deploy even runs. Use a fallback so an unset variable still resolves to a string: > > ```typescript -> clientSecret: process.env.GOOGLE_CLIENT_SECRET!, // ❌ throws at deploy time if the var is unset — `!` only silences TypeScript, it doesn't change the runtime value -> clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "", // ✅ falls back to "" when the var is unset, e.g. in CI +> clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "", // Falls back to "" when the var is unset, e.g. in CI > ``` > > With the `.env` file itself, an explicit empty assignment (`GOOGLE_CLIENT_SECRET=`) also works, since that sets the variable to `""` rather than leaving it unset.