diff --git a/CHANGELOG.md b/CHANGELOG.md index 24e03b1ad..b195c482c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added - [Experimental][Sourcebot EE] Added GitLab permission syncing. [#585](https://github.com/sourcebot-dev/sourcebot/pull/585) +- [Sourcebot EE] Added external identity provider config and support for multiple accounts. [#595](https://github.com/sourcebot-dev/sourcebot/pull/595) ### Fixed - [ask sb] Fixed issue where reasoning tokens would appear in `text` content for openai compatible models. [#582](https://github.com/sourcebot-dev/sourcebot/pull/582) diff --git a/docs/docs.json b/docs/docs.json index 3a2cb43f6..441f4139a 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -79,6 +79,7 @@ ] }, "docs/configuration/language-model-providers", + "docs/configuration/idp", { "group": "Authentication", "pages": [ diff --git a/docs/docs/configuration/auth/providers.mdx b/docs/docs/configuration/auth/providers.mdx index c3d54363f..91328b373 100644 --- a/docs/docs/configuration/auth/providers.mdx +++ b/docs/docs/configuration/auth/providers.mdx @@ -26,95 +26,5 @@ See [transactional emails](/docs/configuration/transactional-emails) for more de # Enterprise Authentication Providers -The following authentication providers require an [enterprise license](/docs/license-key) to be enabled. - -### GitHub ---- - -[Auth.js GitHub Provider Docs](https://authjs.dev/getting-started/providers/github) - -Authentication using both a **GitHub OAuth App** and a **GitHub App** is supported. In both cases, you must provide Sourcebot the `CLIENT_ID` and `SECRET_ID` and configure the -callback URL correctly (more info in Auth.js docs). - -When using a **GitHub App** for auth, enable the following permissions: -- `“Email addresses” account permissions (read)` -- `"Metadata" repository permissions (read)` (only needed if enabling [permission syncing](/docs/features/permission-syncing)) - -**Required environment variables:** -- `AUTH_EE_GITHUB_CLIENT_ID` -- `AUTH_EE_GITHUB_CLIENT_SECRET` - -Optional environment variables: -- `AUTH_EE_GITHUB_BASE_URL` - Base URL for GitHub Enterprise (defaults to https://github.com) - -### GitLab ---- - -[Auth.js GitLab Provider Docs](https://authjs.dev/getting-started/providers/gitlab) - -Authentication using GitLab is supported via a [OAuth2.0 app](https://docs.gitlab.com/integration/oauth_provider/#create-an-instance-wide-application) installed on the GitLab instance. Follow the instructions in the [GitLab docs](https://docs.gitlab.com/integration/oauth_provider/) to create an app. The callback URL should be configurd to `/api/auth/callback/gitlab`, and the following scopes need to be set: - -| Scope | Required | Notes | -|------------|----------|----------------------------------------------------------------------------------------------------| -| read_user | Yes | Allows Sourcebot to read basic user information required for authentication. | -| read_api | Conditional | Required **only** when [permission syncing](/docs/features/permission-syncing) is enabled. Enables Sourcebot to list all repositories and projects for the authenticated user. | - - -**Required environment variables:** -- `AUTH_EE_GITLAB_CLIENT_ID` -- `AUTH_EE_GITLAB_CLIENT_SECRET` - -Optional environment variables: -- `AUTH_EE_GITLAB_BASE_URL` - Base URL for GitLab instance (defaults to https://gitlab.com) - -### Google ---- - -[Auth.js Google Provider Docs](https://authjs.dev/getting-started/providers/google) - -**Required environment variables:** -- `AUTH_EE_GOOGLE_CLIENT_ID` -- `AUTH_EE_GOOGLE_CLIENT_SECRET` - -### GCP IAP ---- - -If you're running Sourcebot in an environment that blocks egress, make sure you allow the [IAP IP ranges](https://www.gstatic.com/ipranges/goog.json) - -Custom provider built to enable automatic Sourcebot account registration/login when using GCP IAP. - -**Required environment variables** -- `AUTH_EE_GCP_IAP_ENABLED` -- `AUTH_EE_GCP_IAP_AUDIENCE` - - This can be found by selecting the ⋮ icon next to the IAP-enabled backend service and pressing `Get JWT audience code` - -### Okta ---- - -[Auth.js Okta Provider Docs](https://authjs.dev/getting-started/providers/okta) - -**Required environment variables:** -- `AUTH_EE_OKTA_CLIENT_ID` -- `AUTH_EE_OKTA_CLIENT_SECRET` -- `AUTH_EE_OKTA_ISSUER` - -### Keycloak ---- - -[Auth.js Keycloak Provider Docs](https://authjs.dev/getting-started/providers/keycloak) - -**Required environment variables:** -- `AUTH_EE_KEYCLOAK_CLIENT_ID` -- `AUTH_EE_KEYCLOAK_CLIENT_SECRET` -- `AUTH_EE_KEYCLOAK_ISSUER` - -### Microsoft Entra ID - -[Auth.js Microsoft Entra ID Provider Docs](https://authjs.dev/getting-started/providers/microsoft-entra-id) - -**Required environment variables:** -- `AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID` -- `AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET` -- `AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER` - ---- \ No newline at end of file +Sourcebot supports authentication using several different [external identity providers](/docs/configuration/idp) as well. These identity providers require an +[enterprise license](/docs/license-key) \ No newline at end of file diff --git a/docs/docs/configuration/idp.mdx b/docs/docs/configuration/idp.mdx new file mode 100644 index 000000000..af4f8e053 --- /dev/null +++ b/docs/docs/configuration/idp.mdx @@ -0,0 +1,371 @@ +--- +title: External Identity Providers +sidebarTitle: External identity providers +--- + +import LicenseKeyRequired from '/snippets/license-key-required.mdx' + + + +You can connect Sourcebot to various **external identity providers** to associate a Sourcebot user with one or more external service accounts (ex. Google, GitHub, etc). + +External identity providers can be used for [authentication](/docs/configuration/auth) and/or [permission syncing](/docs/features/permission-syncing). They're defined in the +[config file](/docs/configuration/config-file) in the top-level `identityProviders` object: + +```json wrap icon="code" Example config with both google and github identity providers defined +{ + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "github", + "purpose": "account_linking", + "accountLinkingRequired": true, +/* +Secrets are provided through environment variables. Set the secret into +an env var and provide the name here to tell Sourcebot where to get +the value +*/ + "clientId": { + "env": "GITHUB_IDENTITY_PROVIDER_CLIENT_ID" + }, + "clientSecret": { + "env": "GITHUB_IDENTITY_PROVIDER_CLIENT_SECRET" + } + }, + { + "provider": "google", + "clientId": { + "env": "GOOGLE_IDENTITY_PROVIDER_CLIENT_ID" + }, + "clientSecret": { + "env": "GOOGLE_IDENTITY_PROVIDER_CLIENT_SECRET" + } + } + ] +} +``` + +# Supported External Identity Providers + +Sourcebot uses [Auth.js](https://authjs.dev/) to connect to external identity providers. If there's a provider supported by Auth.js that you don't see below, please submit a +[feature request](https://github.com/sourcebot-dev/sourcebot/issues) to have it added. + +### GitHub + +[Auth.js GitHub Provider Docs](https://authjs.dev/getting-started/providers/github) + +A GitHub connection can be used for either [authentication](/docs/configuration/auth) or [permission syncing](/docs/features/permission-syncing). This is controlled using the `purpose` field +in the GitHub identity provider config. + + + + + To begin, you must register an Oauth client in GitHub to faciliate the identity provider connection. You can do this by creating a **GitHub App** or a **GitHub OAuth App**. Either + one works, but the **GitHub App** is the [recommended mechanism](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps). + + + The result of registering an OAuth client is a `CLIENT_ID` and `CLIENT_SECRET` which you'll provide to Sourcebot. + + + You don't need to install the app to use it as an external identity provider + Follow [this guide](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) to register a new GitHub App. + + When asked to provide a callback url, provide `/api/auth/callback/github` (ex. https://sourcebot.coolcorp.com/api/auth/callback/github) + + Set the following fine-grained permissions in the GitHub App: + - `“Email addresses” account permissions (read)` + - `"Metadata" repository permissions (read)` (only needed if using permission syncing) + + + Follow [this guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) by GitHub to create an OAuth App. + + When asked to provide a callback url, provide `/api/auth/callback/github` (ex. https://sourcebot.coolcorp.com/api/auth/callback/github) + + + + + To provide Sourcebot the client id and secret for your OAuth client you must set them as environment variables. These can be named whatever you like + (ex. `GITHUB_IDENTITY_PROVIDER_CLIENT_ID` and `GITHUB_IDENTITY_PROVIDER_CLIENT_SECRET`) + + + Finally, pass the client id and secret to Sourcebot by defining a `identityProvider` object in the [config file](/docs/configuration/config-file): + + ```json wrap icon="code" + { + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "github", + // "sso" for auth + perm sync, "account_linking" for only perm sync + "purpose": "account_linking", + // if purpose == "account_linking" this controls if a user must connect to the IdP + "accountLinkingRequired": true, + "clientId": { + "env": "YOUR_CLIENT_ID_ENV_VAR" + }, + "clientSecret": { + "env": "YOUR_CLIENT_SECRET_ENV_VAR" + } + } + ] + } + ``` + + + + +### GitLab + +[Auth.js GitLab Provider Docs](https://authjs.dev/getting-started/providers/gitlab) + +A GitLab connection can be used for either [authentication](/docs/configuration/auth) or [permission syncing](/docs/features/permission-syncing). This is controlled using the `purpose` field +in the GitLab identity provider config. + + + + + To begin, you must register an OAuth application in GitLab to facilitate the identity provider connection. + + Follow [this guide](https://docs.gitlab.com/integration/oauth_provider/) by GitLab to create an OAuth application. + + When configuring your application: + - Set the callback URL to `/api/auth/callback/gitlab` (ex. https://sourcebot.coolcorp.com/api/auth/callback/gitlab) + - Enable the `read_user` scope + - If using for permission syncing, also enable the `read_api` scope + + The result of registering an OAuth application is an `APPLICATION_ID` (`CLIENT_ID`) and `SECRET` (`CLIENT_SECRET`) which you'll provide to Sourcebot. + + + To provide Sourcebot the client id and secret for your OAuth application you must set them as environment variables. These can be named whatever you like + (ex. `GITLAB_IDENTITY_PROVIDER_CLIENT_ID` and `GITLAB_IDENTITY_PROVIDER_CLIENT_SECRET`) + + + Finally, pass the client id and secret to Sourcebot by defining a `identityProvider` object in the [config file](/docs/configuration/config-file): + + ```json wrap icon="code" + { + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "gitlab", + // "sso" for auth + perm sync, "account_linking" for only perm sync + "purpose": "account_linking", + // if purpose == "account_linking" this controls if a user must connect to the IdP + "accountLinkingRequired": true, + "clientId": { + "env": "YOUR_CLIENT_ID_ENV_VAR" + }, + "clientSecret": { + "env": "YOUR_CLIENT_SECRET_ENV_VAR" + }, + // Optional: for self-hosted GitLab instances + "baseUrl": "https://gitlab.example.com" + } + ] + } + ``` + + + + +### Google + +[Auth.js Google Provider Docs](https://authjs.dev/getting-started/providers/google) + +A Google connection can be used for [authentication](/docs/configuration/auth). + + + + + To begin, you must register an OAuth client in Google Cloud Console to facilitate the identity provider connection. + + Follow [this guide](https://support.google.com/cloud/answer/6158849) by Google to create OAuth 2.0 credentials. + + When configuring your OAuth client: + - Set the application type to "Web application" + - Add `/api/auth/callback/google` to the authorized redirect URIs (ex. https://sourcebot.coolcorp.com/api/auth/callback/google) + + The result of creating OAuth credentials is a `CLIENT_ID` and `CLIENT_SECRET` which you'll provide to Sourcebot. + + + To provide Sourcebot the client id and secret for your OAuth client you must set them as environment variables. These can be named whatever you like + (ex. `GOOGLE_IDENTITY_PROVIDER_CLIENT_ID` and `GOOGLE_IDENTITY_PROVIDER_CLIENT_SECRET`) + + + Finally, pass the client id and secret to Sourcebot by defining a `identityProvider` object in the [config file](/docs/configuration/config-file): + + ```json wrap icon="code" + { + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "google", + "purpose": "sso", + "clientId": { + "env": "YOUR_CLIENT_ID_ENV_VAR" + }, + "clientSecret": { + "env": "YOUR_CLIENT_SECRET_ENV_VAR" + } + } + ] + } + ``` + + + + +### Okta + +[Auth.js Okta Provider Docs](https://authjs.dev/getting-started/providers/okta) + +An Okta connection can be used for [authentication](/docs/configuration/auth). + + + + + To begin, you must register an OAuth application in Okta to facilitate the identity provider connection. + + Follow [this guide](https://developer.okta.com/docs/guides/implement-oauth-for-okta/main/) by Okta to create an OAuth application. + + When configuring your application: + - Set the application type to "Web Application" + - Add `/api/auth/callback/okta` to the sign-in redirect URIs (ex. https://sourcebot.coolcorp.com/api/auth/callback/okta) + + The result of creating an OAuth application is a `CLIENT_ID`, `CLIENT_SECRET`, and `ISSUER` URL which you'll provide to Sourcebot. + + + To provide Sourcebot the client id, client secret, and issuer for your OAuth application you must set them as environment variables. These can be named whatever you like + (ex. `OKTA_IDENTITY_PROVIDER_CLIENT_ID`, `OKTA_IDENTITY_PROVIDER_CLIENT_SECRET`, and `OKTA_IDENTITY_PROVIDER_ISSUER`) + + + Finally, pass the client id, client secret, and issuer to Sourcebot by defining a `identityProvider` object in the [config file](/docs/configuration/config-file): + + ```json wrap icon="code" + { + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "okta", + "purpose": "sso", + "clientId": { + "env": "YOUR_CLIENT_ID_ENV_VAR" + }, + "clientSecret": { + "env": "YOUR_CLIENT_SECRET_ENV_VAR" + }, + "issuer": { + "env": "YOUR_ISSUER_ENV_VAR" + } + } + ] + } + ``` + + + + +### Keycloak + +[Auth.js Keycloak Provider Docs](https://authjs.dev/getting-started/providers/keycloak) + +A Keycloak connection can be used for [authentication](/docs/configuration/auth). + + + + + To begin, you must register an OAuth client in Keycloak to facilitate the identity provider connection. + + Follow [this guide](https://www.keycloak.org/docs/latest/server_admin/#_oidc_clients) by Keycloak to create an OpenID Connect client. + + When configuring your client: + - Set the client protocol to "openid-connect" + - Set the access type to "confidential" + - Add `/api/auth/callback/keycloak` to the valid redirect URIs (ex. https://sourcebot.coolcorp.com/api/auth/callback/keycloak) + + The result of creating an OAuth client is a `CLIENT_ID`, `CLIENT_SECRET`, and an `ISSUER` URL (typically in the format `https:///realms/`) which you'll provide to Sourcebot. + + + To provide Sourcebot the client id, client secret, and issuer for your OAuth client you must set them as environment variables. These can be named whatever you like + (ex. `KEYCLOAK_IDENTITY_PROVIDER_CLIENT_ID`, `KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET`, and `KEYCLOAK_IDENTITY_PROVIDER_ISSUER`) + + + Finally, pass the client id, client secret, and issuer to Sourcebot by defining a `identityProvider` object in the [config file](/docs/configuration/config-file): + + ```json wrap icon="code" + { + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "keycloak", + "purpose": "sso", + "clientId": { + "env": "YOUR_CLIENT_ID_ENV_VAR" + }, + "clientSecret": { + "env": "YOUR_CLIENT_SECRET_ENV_VAR" + }, + "issuer": { + "env": "YOUR_ISSUER_ENV_VAR" + } + } + ] + } + ``` + + + + +### Microsoft Entra ID + +[Auth.js Microsoft Entra ID Provider Docs](https://authjs.dev/getting-started/providers/microsoft-entra-id) + +A Microsoft Entra ID connection can be used for [authentication](/docs/configuration/auth). + + + + + To begin, you must register an OAuth application in Microsoft Entra ID (formerly Azure Active Directory) to facilitate the identity provider connection. + + Follow [this guide](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) by Microsoft to register an application. + + When configuring your application: + - Under "Authentication", add a platform and select "Web" + - Set the redirect URI to `/api/auth/callback/microsoft-entra-id` (ex. https://sourcebot.coolcorp.com/api/auth/callback/microsoft-entra-id) + - Under "Certificates & secrets", create a new client secret + + The result of registering an application is a `CLIENT_ID` (Application ID), `CLIENT_SECRET`, and `TENANT_ID` which you'll use to construct the issuer URL. + + + To provide Sourcebot the client id, client secret, and issuer for your OAuth application you must set them as environment variables. These can be named whatever you like + (ex. `MICROSOFT_ENTRA_ID_IDENTITY_PROVIDER_CLIENT_ID`, `MICROSOFT_ENTRA_ID_IDENTITY_PROVIDER_CLIENT_SECRET`, and `MICROSOFT_ENTRA_ID_IDENTITY_PROVIDER_ISSUER`) + + The issuer URL should be in the format: `https://login.microsoftonline.com//v2.0` + + + Finally, pass the client id, client secret, and issuer to Sourcebot by defining a `identityProvider` object in the [config file](/docs/configuration/config-file): + + ```json wrap icon="code" + { + "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json", + "identityProviders": [ + { + "provider": "microsoft-entra-id", + "purpose": "sso", + "clientId": { + "env": "YOUR_CLIENT_ID_ENV_VAR" + }, + "clientSecret": { + "env": "YOUR_CLIENT_SECRET_ENV_VAR" + }, + "issuer": { + "env": "YOUR_ISSUER_ENV_VAR" + } + } + ] + } + ``` + + + + diff --git a/docs/docs/features/permission-syncing.mdx b/docs/docs/features/permission-syncing.mdx index ee6a96e70..403f534f1 100644 --- a/docs/docs/features/permission-syncing.mdx +++ b/docs/docs/features/permission-syncing.mdx @@ -12,10 +12,12 @@ import ExperimentalFeatureWarning from '/snippets/experimental-feature-warning.m # Overview -Permission syncing allows you to sync Access Permission Lists (ACLs) from a code host to Sourcebot. When configured, users signed into Sourcebot (via the code host's OAuth provider) will only be able to access repositories that they have access to on the code host. Practically, this means: +Permission syncing allows you to sync Access Permission Lists (ACLs) from a code host to Sourcebot. When configured, users signed into Sourcebot will only be able to access repositories +that they have access to on the code host. Practically, this means: - Code Search results will only include repositories that the user has access to. - Code navigation results will only include repositories that the user has access to. +- MCP results will only include results from repositories the user has access to. - Ask Sourcebot (and the underlying LLM) will only have access to repositories that the user has access to. - File browsing is scoped to the repositories that the user has access to. @@ -46,7 +48,7 @@ We are actively working on supporting more code hosts. If you'd like to see a sp ## GitHub -Prerequisite: [Add GitHub as an OAuth provider](/docs/configuration/auth/providers#github). +Prerequisite: Configure GitHub as an [external identity provider](/docs/configuration/idp). Permission syncing works with **GitHub.com**, **GitHub Enterprise Cloud**, and **GitHub Enterprise Server**. For organization-owned repositories, users that have **read-only** access (or above) via the following methods will have their access synced to Sourcebot: - Outside collaborators @@ -56,18 +58,18 @@ Permission syncing works with **GitHub.com**, **GitHub Enterprise Cloud**, and * - Organization owners. **Notes:** -- A GitHub OAuth provider must be configured to (1) correlate a Sourcebot user with a GitHub user, and (2) to list repositories that the user has access to for [User driven syncing](/docs/features/permission-syncing#how-it-works). +- A GitHub [external identity provider](/docs/configuration/idp) must be configured to (1) correlate a Sourcebot user with a GitHub user, and (2) to list repositories that the user has access to for [User driven syncing](/docs/features/permission-syncing#how-it-works). - OAuth tokens must assume the `repo` scope in order to use the [List repositories for the authenticated user API](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user) during [User driven syncing](/docs/features/permission-syncing#how-it-works). Sourcebot **will only** use this token for **reads**. ## GitLab -Prerequisite: [Add GitLab as an OAuth provider](/docs/configuration/auth/providers#gitlab). +Prerequisite: Configure GitLab as an [external identity provider](/docs/configuration/idp). Permission syncing works with **GitLab Self-managed** and **GitLab Cloud**. Users with **Guest** role or above with membership to a group or project will have their access synced to Sourcebot. Both direct and indirect membership to a group or project will be synced with Sourcebot. For more details, see the [GitLab docs](https://docs.gitlab.com/user/project/members/#membership-types). **Notes:** -- A GitLab OAuth provider must be configured to (1) correlate a Sourcebot user with a GitLab user, and (2) to list repositories that the user has access to for [User driven syncing](/docs/features/permission-syncing#how-it-works). +- A GitLab [external identity provider](/docs/configuration/idp) must be configured to (1) correlate a Sourcebot user with a GitLab user, and (2) to list repositories that the user has access to for [User driven syncing](/docs/features/permission-syncing#how-it-works). - OAuth tokens require the `read_api` scope in order to use the [List projects for the authenticated user API](https://docs.gitlab.com/ee/api/projects.html#list-all-projects) during [User driven syncing](/docs/features/permission-syncing#how-it-works). diff --git a/docs/docs/license-key.mdx b/docs/docs/license-key.mdx index 272eab625..4b13fcaf3 100644 --- a/docs/docs/license-key.mdx +++ b/docs/docs/license-key.mdx @@ -7,7 +7,7 @@ sidebarTitle: License key If you'd like a trial license, [reach out](https://www.sourcebot.dev/contact) and we'll send one over within 24 hours -All core Sourcebot features are available [FSL licensed](https://github.com/sourcebot-dev/sourcebot/blob/main/LICENSE.md#functional-source-license-version-11-alv2-future-license) without any limits. Some additional features require a license key. See the [pricing page](https://www.sourcebot.dev/pricing) for more details. +All core Sourcebot features are available under the [FSL license](https://github.com/sourcebot-dev/sourcebot/blob/main/LICENSE.md#functional-source-license-version-11-alv2-future-license). Some additional features require a license key. See the [pricing page](https://www.sourcebot.dev/pricing) for more details. ## Activating a license key @@ -25,7 +25,7 @@ docker run \ ## Feature availability --- -| Feature | OSS | Licensed | +| Feature | [FSL](https://github.com/sourcebot-dev/sourcebot/blob/main/LICENSE.md#functional-source-license-version-11-alv2-future-license) | [Enterprise](https://github.com/sourcebot-dev/sourcebot/blob/main/ee/LICENSE) | |:---------|:-----|:----------| | [Search](/docs/features/search/syntax-reference) | ✅ | ✅ | | [Full code host support](/docs/connections/overview) | ✅ | ✅ | @@ -34,6 +34,7 @@ docker run \ | [Login with credentials](/docs/configuration/auth/overview) | ✅ | ✅ | | [Login with email codes](/docs/configuration/auth/overview) | ✅ | ✅ | | [Login with SSO](/docs/configuration/auth/overview#enterprise-authentication-providers) | 🛑 | ✅ | +| [Permission syncing](/docs/features/permission-syncing) | 🛑 | ✅ | | [Code navigation](/docs/features/code-navigation) | 🛑 | ✅ | | [Search contexts](/docs/features/search/search-contexts) | 🛑 | ✅ | | [Audit logs](/docs/configuration/audit-logs) | 🛑 | ✅ | diff --git a/docs/snippets/schemas/v3/app.schema.mdx b/docs/snippets/schemas/v3/app.schema.mdx index 2b2090b51..07fd910c9 100644 --- a/docs/snippets/schemas/v3/app.schema.mdx +++ b/docs/snippets/schemas/v3/app.schema.mdx @@ -8,7 +8,7 @@ "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { @@ -70,7 +70,7 @@ "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { diff --git a/docs/snippets/schemas/v3/identityProvider.schema.mdx b/docs/snippets/schemas/v3/identityProvider.schema.mdx new file mode 100644 index 000000000..30c172bed --- /dev/null +++ b/docs/snippets/schemas/v3/identityProvider.schema.mdx @@ -0,0 +1,1299 @@ +{/* THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! */} +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + }, + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + ] +} +``` diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 2a9fe6027..615c058a8 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -4280,7 +4280,7 @@ "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { @@ -4342,7 +4342,7 @@ "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { @@ -4400,6 +4400,1306 @@ } ] } + }, + "identityProviders": { + "type": "array", + "description": "Defines a collection of identity providers that are available to Sourcebot.", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + }, + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + ] + } } }, "additionalProperties": false diff --git a/packages/backend/src/ee/githubAppManager.ts b/packages/backend/src/ee/githubAppManager.ts index d8a72dff5..892e637bd 100644 --- a/packages/backend/src/ee/githubAppManager.ts +++ b/packages/backend/src/ee/githubAppManager.ts @@ -50,7 +50,7 @@ export class GithubAppManager { return; } - const githubApps = config.apps.filter(app => app.type === 'githubApp') as GitHubAppConfig[]; + const githubApps = config.apps.filter(app => app.type === 'github') as GitHubAppConfig[]; logger.info(`Found ${githubApps.length} GitHub apps in config`); for (const app of githubApps) { diff --git a/packages/schemas/src/v3/app.schema.ts b/packages/schemas/src/v3/app.schema.ts index 0a37104a7..b46bd72a3 100644 --- a/packages/schemas/src/v3/app.schema.ts +++ b/packages/schemas/src/v3/app.schema.ts @@ -7,7 +7,7 @@ const schema = { "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { @@ -69,7 +69,7 @@ const schema = { "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { diff --git a/packages/schemas/src/v3/app.type.ts b/packages/schemas/src/v3/app.type.ts index be5996ba4..325084a8f 100644 --- a/packages/schemas/src/v3/app.type.ts +++ b/packages/schemas/src/v3/app.type.ts @@ -6,7 +6,7 @@ export interface GitHubAppConfig { /** * GitHub App Configuration */ - type: "githubApp"; + type: "github"; /** * The hostname of the GitHub App deployment. */ diff --git a/packages/schemas/src/v3/identityProvider.schema.ts b/packages/schemas/src/v3/identityProvider.schema.ts new file mode 100644 index 000000000..708c3b4cd --- /dev/null +++ b/packages/schemas/src/v3/identityProvider.schema.ts @@ -0,0 +1,1298 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! +const schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + }, + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + ] +} as const; +export { schema as identityProviderSchema }; \ No newline at end of file diff --git a/packages/schemas/src/v3/identityProvider.type.ts b/packages/schemas/src/v3/identityProvider.type.ts new file mode 100644 index 000000000..31d7a9a8c --- /dev/null +++ b/packages/schemas/src/v3/identityProvider.type.ts @@ -0,0 +1,257 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! + +export type IdentityProviderConfig = + | GitHubIdentityProviderConfig + | GitLabIdentityProviderConfig + | GoogleIdentityProviderConfig + | OktaIdentityProviderConfig + | KeycloakIdentityProviderConfig + | MicrosoftEntraIDIdentityProviderConfig + | GCPIAPIdentityProviderConfig; + +export interface GitHubIdentityProviderConfig { + provider: "github"; + purpose: "sso" | "account_linking"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + /** + * The URL of the GitHub host. Defaults to https://github.com + */ + baseUrl?: string; + accountLinkingRequired?: boolean; +} +export interface GitLabIdentityProviderConfig { + provider: "gitlab"; + purpose: "sso" | "account_linking"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + /** + * The URL of the GitLab host. Defaults to https://gitlab.com + */ + baseUrl?: string; + accountLinkingRequired?: boolean; +} +export interface GoogleIdentityProviderConfig { + provider: "google"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface OktaIdentityProviderConfig { + provider: "okta"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + issuer: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface KeycloakIdentityProviderConfig { + provider: "keycloak"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + issuer: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface MicrosoftEntraIDIdentityProviderConfig { + provider: "microsoft-entra-id"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + issuer: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface GCPIAPIdentityProviderConfig { + provider: "gcp-iap"; + purpose: "sso"; + audience: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index 897f68268..ee0452435 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -4279,7 +4279,7 @@ const schema = { "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { @@ -4341,7 +4341,7 @@ const schema = { "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { @@ -4399,6 +4399,1306 @@ const schema = { } ] } + }, + "identityProviders": { + "type": "array", + "description": "Defines a collection of identity providers that are available to Sourcebot.", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + }, + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": [ + "sso", + "account_linking" + ] + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "clientSecret": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + }, + "issuer": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "provider", + "purpose", + "audience" + ] + } + ] + } } }, "additionalProperties": false diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index ddda62d74..0e88d8ab7 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -26,6 +26,14 @@ export type LanguageModel = | OpenRouterLanguageModel | XaiLanguageModel; export type AppConfig = GitHubAppConfig; +export type IdentityProviderConfig = + | GitHubIdentityProviderConfig + | GitLabIdentityProviderConfig + | GoogleIdentityProviderConfig + | OktaIdentityProviderConfig + | KeycloakIdentityProviderConfig + | MicrosoftEntraIDIdentityProviderConfig + | GCPIAPIdentityProviderConfig; export interface SourcebotConfig { $schema?: string; @@ -50,6 +58,10 @@ export interface SourcebotConfig { * Defines a collection of apps that are available to Sourcebot. */ apps?: AppConfig[]; + /** + * Defines a collection of identity providers that are available to Sourcebot. + */ + identityProviders?: IdentityProviderConfig[]; } /** * Defines the global settings for Sourcebot. @@ -1078,7 +1090,7 @@ export interface GitHubAppConfig { /** * GitHub App Configuration */ - type: "githubApp"; + type: "github"; /** * The hostname of the GitHub App deployment. */ @@ -1104,3 +1116,249 @@ export interface GitHubAppConfig { googleCloudSecret: string; }; } +export interface GitHubIdentityProviderConfig { + provider: "github"; + purpose: "sso" | "account_linking"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + /** + * The URL of the GitHub host. Defaults to https://github.com + */ + baseUrl?: string; + accountLinkingRequired?: boolean; +} +export interface GitLabIdentityProviderConfig { + provider: "gitlab"; + purpose: "sso" | "account_linking"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + /** + * The URL of the GitLab host. Defaults to https://gitlab.com + */ + baseUrl?: string; + accountLinkingRequired?: boolean; +} +export interface GoogleIdentityProviderConfig { + provider: "google"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface OktaIdentityProviderConfig { + provider: "okta"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + issuer: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface KeycloakIdentityProviderConfig { + provider: "keycloak"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + issuer: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface MicrosoftEntraIDIdentityProviderConfig { + provider: "microsoft-entra-id"; + purpose: "sso"; + clientId: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + clientSecret: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + issuer: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} +export interface GCPIAPIdentityProviderConfig { + provider: "gcp-iap"; + purpose: "sso"; + audience: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; +} diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index f7c1b56e0..4eb454bdb 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -81,7 +81,11 @@ export const loadJsonFile = async ( } } -export const loadConfig = async (configPath: string): Promise => { +export const loadConfig = async (configPath?: string): Promise => { + if (!configPath) { + throw new Error('CONFIG_PATH is required but not provided'); + } + const configContent = await (async () => { if (isRemotePath(configPath)) { const response = await fetch(configPath); diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx index b60a50a64..a3079d7f0 100644 --- a/packages/web/src/app/[domain]/layout.tsx +++ b/packages/web/src/app/[domain]/layout.tsx @@ -7,7 +7,7 @@ import { UpgradeGuard } from "./components/upgradeGuard"; import { cookies, headers } from "next/headers"; import { getSelectorsByUserAgent } from "react-device-detect"; import { MobileUnsupportedSplashScreen } from "./components/mobileUnsupportedSplashScreen"; -import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME } from "@/lib/constants"; +import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants"; import { SyntaxReferenceGuide } from "./components/syntaxReferenceGuide"; import { SyntaxGuideProvider } from "./components/syntaxGuideProvider"; import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; @@ -23,6 +23,8 @@ import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard"; import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; import { GitHubStarToast } from "./components/githubStarToast"; import { UpgradeToast } from "./components/upgradeToast"; +import { getLinkedAccountProviderStates } from "@/ee/features/permissionSyncing/actions"; +import { LinkAccounts } from "@/ee/features/permissionSyncing/components/linkAccounts"; interface LayoutProps { children: React.ReactNode, @@ -123,6 +125,42 @@ export default async function Layout(props: LayoutProps) { ) } + if (hasEntitlement("permission-syncing")) { + const linkedAccountProviderStates = await getLinkedAccountProviderStates(); + if (isServiceError(linkedAccountProviderStates)) { + return ( +
+ +
+

An error occurred

+

+ {typeof linkedAccountProviderStates.message === 'string' + ? linkedAccountProviderStates.message + : "A server error occurred while checking your account status. Please try again or contact support."} +

+
+
+ ) + } + + const hasUnlinkedProviders = linkedAccountProviderStates.some(state => state.isLinked === false); + if (hasUnlinkedProviders) { + const cookieStore = await cookies(); + const hasSkippedOptional = cookieStore.has(OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME); + + const hasUnlinkedRequiredProviders = linkedAccountProviderStates.some(state => state.required && !state.isLinked) + const shouldShowLinkAccounts = hasUnlinkedRequiredProviders || !hasSkippedOptional; + if (shouldShowLinkAccounts) { + return ( +
+ + +
+ ) + } + } + } + if (IS_BILLING_ENABLED) { const subscription = await getSubscriptionInfo(domain); if ( diff --git a/packages/web/src/app/[domain]/settings/layout.tsx b/packages/web/src/app/[domain]/settings/layout.tsx index ccac2e99e..b0aa8f5b6 100644 --- a/packages/web/src/app/[domain]/settings/layout.tsx +++ b/packages/web/src/app/[domain]/settings/layout.tsx @@ -11,6 +11,7 @@ import { ServiceErrorException } from "@/lib/serviceError"; import { getOrgFromDomain } from "@/data/org"; import { OrgRole } from "@prisma/client"; import { env } from "@/env.mjs"; +import { hasEntitlement } from "@sourcebot/shared"; interface LayoutProps { children: React.ReactNode; @@ -68,6 +69,8 @@ export default async function SettingsLayout( throw new ServiceErrorException(connectionStats); } + const hasPermissionSyncingEntitlement = await hasEntitlement("permission-syncing"); + const sidebarNavItems: SidebarNavItem[] = [ { title: "General", @@ -114,6 +117,12 @@ export default async function SettingsLayout( title: "Analytics", href: `/${domain}/settings/analytics`, }, + ...(hasPermissionSyncingEntitlement ? [ + { + title: "Linked Accounts", + href: `/${domain}/settings/permission-syncing`, + } + ] : []), ...(env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [ { title: "License", diff --git a/packages/web/src/app/[domain]/settings/permission-syncing/page.tsx b/packages/web/src/app/[domain]/settings/permission-syncing/page.tsx new file mode 100644 index 000000000..d9c136c9c --- /dev/null +++ b/packages/web/src/app/[domain]/settings/permission-syncing/page.tsx @@ -0,0 +1,12 @@ +import { hasEntitlement } from "@sourcebot/shared"; +import { notFound } from "next/navigation" +import { LinkedAccountsSettings } from "@/ee/features/permissionSyncing/components/linkedAccountsSettings"; + +export default async function PermissionSyncingPage() { + const hasPermissionSyncingEntitlement = await hasEntitlement("permission-syncing"); + if (!hasPermissionSyncingEntitlement) { + return notFound(); + } + + return ; +} diff --git a/packages/web/src/app/components/authMethodSelector.tsx b/packages/web/src/app/components/authMethodSelector.tsx index 84d1228e2..442fb919a 100644 --- a/packages/web/src/app/components/authMethodSelector.tsx +++ b/packages/web/src/app/components/authMethodSelector.tsx @@ -8,10 +8,10 @@ import { CredentialsForm } from "@/app/login/components/credentialsForm"; import { DividerSet } from "@/app/components/dividerSet"; import { ProviderButton } from "@/app/components/providerButton"; import { AuthSecurityNotice } from "@/app/components/authSecurityNotice"; -import type { AuthProvider } from "@/lib/authProviders"; +import type { IdentityProviderMetadata } from "@/lib/identityProviders"; interface AuthMethodSelectorProps { - providers: AuthProvider[]; + providers: IdentityProviderMetadata[]; callbackUrl?: string; context: "login" | "signup"; onProviderClick?: (providerId: string) => void; @@ -35,11 +35,11 @@ export const AuthMethodSelector = ({ }, [callbackUrl, onProviderClick]); // Separate OAuth providers from special auth methods - const oauthProviders = providers.filter(p => + const oauthProviders = providers.filter(p => p.purpose === "sso" && !["credentials", "nodemailer"].includes(p.id) ); - const hasCredentials = providers.some(p => p.id === "credentials"); - const hasMagicLink = providers.some(p => p.id === "nodemailer"); + const hasCredentials = providers.some(p => p.purpose === "sso" && p.id === "credentials"); + const hasMagicLink = providers.some(p => p.purpose === "sso" && p.id === "nodemailer"); return ( <> diff --git a/packages/web/src/app/invite/page.tsx b/packages/web/src/app/invite/page.tsx index 195a8d177..539bf781a 100644 --- a/packages/web/src/app/invite/page.tsx +++ b/packages/web/src/app/invite/page.tsx @@ -7,7 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { SourcebotLogo } from "@/app/components/sourcebotLogo"; import { AuthMethodSelector } from "@/app/components/authMethodSelector"; import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; -import { getAuthProviders } from "@/lib/authProviders"; +import { getIdentityProviderMetadata, IdentityProviderMetadata } from "@/lib/identityProviders"; import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard"; interface InvitePageProps { @@ -30,7 +30,7 @@ export default async function InvitePage(props: InvitePageProps) { const session = await auth(); if (!session) { - const providers = getAuthProviders(); + const providers = getIdentityProviderMetadata(); return ; } @@ -57,7 +57,7 @@ export default async function InvitePage(props: InvitePageProps) { ); } -function WelcomeCard({ inviteLinkId, providers }: { inviteLinkId: string; providers: import("@/lib/authProviders").AuthProvider[] }) { +function WelcomeCard({ inviteLinkId, providers }: { inviteLinkId: string; providers: IdentityProviderMetadata[] }) { return (
diff --git a/packages/web/src/app/login/components/loginForm.tsx b/packages/web/src/app/login/components/loginForm.tsx index 1d1eb5e30..b13e19fb6 100644 --- a/packages/web/src/app/login/components/loginForm.tsx +++ b/packages/web/src/app/login/components/loginForm.tsx @@ -6,12 +6,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo"; import { AuthMethodSelector } from "@/app/components/authMethodSelector"; import useCaptureEvent from "@/hooks/useCaptureEvent"; import Link from "next/link"; -import type { AuthProvider } from "@/lib/authProviders"; +import type { IdentityProviderMetadata } from "@/lib/identityProviders"; interface LoginFormProps { callbackUrl?: string; error?: string; - providers: AuthProvider[]; + providers: IdentityProviderMetadata[]; context: "login" | "signup"; } diff --git a/packages/web/src/app/login/page.tsx b/packages/web/src/app/login/page.tsx index 1535ec589..2ce955c5e 100644 --- a/packages/web/src/app/login/page.tsx +++ b/packages/web/src/app/login/page.tsx @@ -2,7 +2,7 @@ import { auth } from "@/auth"; import { LoginForm } from "./components/loginForm"; import { redirect } from "next/navigation"; import { Footer } from "@/app/components/footer"; -import { getAuthProviders } from "@/lib/authProviders"; +import { getIdentityProviderMetadata } from "@/lib/identityProviders"; import { getOrgFromDomain } from "@/data/org"; import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"; @@ -25,7 +25,7 @@ export default async function Login(props: LoginProps) { return redirect("/onboard"); } - const providers = getAuthProviders(); + const providers = getIdentityProviderMetadata(); return (
diff --git a/packages/web/src/app/onboard/page.tsx b/packages/web/src/app/onboard/page.tsx index b446c15ff..33077a9be 100644 --- a/packages/web/src/app/onboard/page.tsx +++ b/packages/web/src/app/onboard/page.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button" import { AuthMethodSelector } from "@/app/components/authMethodSelector" import { SourcebotLogo } from "@/app/components/sourcebotLogo" import { auth } from "@/auth"; -import { getAuthProviders } from "@/lib/authProviders"; +import { getIdentityProviderMetadata } from "@/lib/identityProviders"; import { OrganizationAccessSettings } from "@/app/components/organizationAccessSettings"; import { CompleteOnboardingButton } from "./components/completeOnboardingButton"; import { getOrgFromDomain } from "@/data/org"; @@ -41,7 +41,7 @@ interface ResourceCard { export default async function Onboarding(props: OnboardingProps) { const searchParams = await props.searchParams; - const providers = getAuthProviders(); + const providers = getIdentityProviderMetadata(); const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN); const session = await auth(); diff --git a/packages/web/src/app/signup/page.tsx b/packages/web/src/app/signup/page.tsx index dc9205961..fd00c87df 100644 --- a/packages/web/src/app/signup/page.tsx +++ b/packages/web/src/app/signup/page.tsx @@ -3,7 +3,7 @@ import { LoginForm } from "../login/components/loginForm"; import { redirect } from "next/navigation"; import { Footer } from "@/app/components/footer"; import { createLogger } from "@sourcebot/logger"; -import { getAuthProviders } from "@/lib/authProviders"; +import { getIdentityProviderMetadata } from "@/lib/identityProviders"; import { getOrgFromDomain } from "@/data/org"; import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"; @@ -29,7 +29,7 @@ export default async function Signup(props: LoginProps) { return redirect("/onboard"); } - const providers = getAuthProviders(); + const providers = getIdentityProviderMetadata(); return (
diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts index 27800d0c1..64dab588f 100644 --- a/packages/web/src/auth.ts +++ b/packages/web/src/auth.ts @@ -13,39 +13,54 @@ import { createTransport } from 'nodemailer'; import { render } from '@react-email/render'; import MagicLinkEmail from './emails/magicLinkEmail'; import bcrypt from 'bcryptjs'; -import { getSSOProviders } from '@/ee/features/sso/sso'; +import { getEEIdentityProviders } from '@/ee/features/sso/sso'; import { hasEntitlement } from '@sourcebot/shared'; import { onCreateUser } from '@/lib/authUtils'; import { getAuditService } from '@/ee/features/audit/factory'; import { SINGLE_TENANT_ORG_ID } from './lib/constants'; +import { refreshLinkedAccountTokens } from '@/ee/features/permissionSyncing/tokenRefresh'; const auditService = getAuditService(); +const eeIdentityProviders = hasEntitlement("sso") ? await getEEIdentityProviders() : []; export const runtime = 'nodejs'; +export type IdentityProvider = { + provider: Provider; + purpose: "sso" | "account_linking"; + required?: boolean; +} + +export type LinkedAccountToken = { + provider: string; + accessToken: string; + refreshToken: string; + expiresAt: number; + error?: string; +}; +export type LinkedAccountTokensMap = Record; + declare module 'next-auth' { interface Session { user: { id: string; } & DefaultSession['user']; + linkedAccountProviderErrors?: Record; } } declare module 'next-auth/jwt' { interface JWT { - userId: string + userId: string; + linkedAccountTokens?: LinkedAccountTokensMap; } } export const getProviders = () => { - const providers: Provider[] = []; - - if (hasEntitlement("sso")) { - providers.push(...getSSOProviders()); - } + const providers: IdentityProvider[] = eeIdentityProviders; if (env.SMTP_CONNECTION_URL && env.EMAIL_FROM_ADDRESS && env.AUTH_EMAIL_CODE_LOGIN_ENABLED === 'true') { - providers.push(EmailProvider({ + providers.push({ provider: EmailProvider({ server: env.SMTP_CONNECTION_URL, from: env.EMAIL_FROM_ADDRESS, maxAge: 60 * 10, @@ -69,11 +84,11 @@ export const getProviders = () => { throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`); } } - })); + }), purpose: "sso"}); } if (env.AUTH_CREDENTIALS_LOGIN_ENABLED === 'true') { - providers.push(Credentials({ + providers.push({ provider: Credentials({ credentials: { email: {}, password: {} @@ -126,7 +141,7 @@ export const getProviders = () => { }; } } - })); + }), purpose: "sso"}); } return providers; @@ -176,13 +191,30 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ } }, callbacks: { - async jwt({ token, user: _user }) { + async jwt({ token, user: _user, account }) { const user = _user as User | undefined; // @note: `user` will be available on signUp or signIn triggers. // Cache the userId in the JWT for later use. if (user) { token.userId = user.id; } + + if (hasEntitlement('permission-syncing')) { + if (account && account.access_token && account.refresh_token && account.expires_at) { + token.linkedAccountTokens = token.linkedAccountTokens || {}; + token.linkedAccountTokens[account.providerAccountId] = { + provider: account.provider, + accessToken: account.access_token, + refreshToken: account.refresh_token, + expiresAt: account.expires_at, + }; + } + + if (token.linkedAccountTokens) { + token.linkedAccountTokens = await refreshLinkedAccountTokens(token.linkedAccountTokens); + } + } + return token; }, async session({ session, token }) { @@ -193,10 +225,23 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ // Propagate the userId to the session. id: token.userId, } + + // Pass only linked account provider errors to the session (not sensitive tokens) + if (token.linkedAccountTokens) { + const errors: Record = {}; + for (const [providerAccountId, tokenData] of Object.entries(token.linkedAccountTokens)) { + if (tokenData.error) { + errors[providerAccountId] = tokenData.error; + } + } + if (Object.keys(errors).length > 0) { + session.linkedAccountProviderErrors = errors; + } + } return session; }, }, - providers: getProviders(), + providers: getProviders().map((provider) => provider.provider), pages: { signIn: "/login", // We set redirect to false in signInOptions so we can pass the email is as a param diff --git a/packages/web/src/ee/features/permissionSyncing/actions.ts b/packages/web/src/ee/features/permissionSyncing/actions.ts new file mode 100644 index 000000000..956670271 --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/actions.ts @@ -0,0 +1,107 @@ +'use server'; + +import { sew } from "@/actions"; +import { createLogger } from "@sourcebot/logger"; +import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; +import { loadConfig } from "@sourcebot/shared"; +import { env } from "@/env.mjs"; +import { OrgRole } from "@sourcebot/db"; +import { cookies } from "next/headers"; +import { OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants"; +import { LinkedAccountProviderState } from "@/ee/features/permissionSyncing/types"; +import { auth } from "@/auth"; + +const logger = createLogger('web-ee-permission-syncing-actions'); + +export const getLinkedAccountProviderStates = async () => sew(() => + withAuthV2(async ({ prisma, role, user }) => + withMinimumOrgRole(role, OrgRole.MEMBER, async () => { + const config = await loadConfig(env.CONFIG_PATH); + const linkedAccountProviderConfigs = config.identityProviders ?? []; + const linkedAccounts = await prisma.account.findMany({ + where: { + userId: user.id, + provider: { + in: linkedAccountProviderConfigs.map(p => p.provider) + } + }, + select: { + provider: true, + providerAccountId: true + } + }); + + // Fetch the session to get token errors + const session = await auth(); + const providerErrors = session?.linkedAccountProviderErrors; + + const linkedAccountProviderState: LinkedAccountProviderState[] = []; + for (const linkedAccountProviderConfig of linkedAccountProviderConfigs) { + if (linkedAccountProviderConfig.purpose === "account_linking") { + const linkedAccount = linkedAccounts.find( + account => account.provider === linkedAccountProviderConfig.provider + ); + + const isLinked = !!linkedAccount; + const isRequired = linkedAccountProviderConfig.accountLinkingRequired ?? false; + const providerError = linkedAccount ? providerErrors?.[linkedAccount.providerAccountId] : undefined; + + linkedAccountProviderState.push({ + id: linkedAccountProviderConfig.provider, + required: isRequired, + isLinked, + linkedAccountId: linkedAccount?.providerAccountId, + error: providerError + } as LinkedAccountProviderState); + } + } + + return linkedAccountProviderState; + }) + ) +); + + +export const unlinkLinkedAccountProvider = async (provider: string) => sew(() => + withAuthV2(async ({ prisma, role, user }) => + withMinimumOrgRole(role, OrgRole.MEMBER, async () => { + const config = await loadConfig(env.CONFIG_PATH); + const identityProviders = config.identityProviders ?? []; + + const providerConfig = identityProviders.find(idp => idp.provider === provider) + if (!providerConfig || providerConfig.purpose !== "account_linking") { + throw new Error("Provider is not a linked account provider"); + } + + // Delete the account + const result = await prisma.account.deleteMany({ + where: { + provider, + userId: user.id, + }, + }); + + logger.info(`Unlinked account provider ${provider} for user ${user.id}. Deleted ${result.count} account(s).`); + + // If we're unlinking a required identity provider then we want to wipe the optional skip cookie if it exists so that we give the + // user the option of linking optional providers in the same link accounts screen + const isRequired = providerConfig.accountLinkingRequired ?? false; + if (isRequired) { + const cookieStore = await cookies(); + cookieStore.delete(OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME); + } + + return { success: true, count: result.count }; + }) + ) +); + +export const skipOptionalProvidersLink = async () => sew(async () => { + const cookieStore = await cookies(); + cookieStore.set(OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME, 'true', { + httpOnly: false, // Allow client-side access + maxAge: 365 * 24 * 60 * 60, // 1 year in seconds + }); + return true; +}); + diff --git a/packages/web/src/ee/features/permissionSyncing/components/linkAccounts.tsx b/packages/web/src/ee/features/permissionSyncing/components/linkAccounts.tsx new file mode 100644 index 000000000..dd3aa531b --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/linkAccounts.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { skipOptionalProvidersLink } from "@/ee/features/permissionSyncing/actions"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { LinkedAccountProviderCard } from "./linkedAccountProviderCard"; +import { LinkedAccountProviderState } from "@/ee/features/permissionSyncing/types"; + +interface LinkAccountsProps { + linkedAccountProviderStates: LinkedAccountProviderState[] + callbackUrl?: string; +} + +export const LinkAccounts = ({ linkedAccountProviderStates, callbackUrl }: LinkAccountsProps) => { + const router = useRouter(); + const [isSkipping, setIsSkipping] = useState(false); + + const handleSkip = async () => { + setIsSkipping(true); + try { + await skipOptionalProvidersLink(); + } catch (error) { + console.error("Failed to skip optional providers:", error); + } finally { + setIsSkipping(false); + router.refresh() + } + }; + + const canSkip = !linkedAccountProviderStates.some(state => state.required && !state.isLinked); + return ( + + + Connect Your Accounts + + Link the following accounts to enable permission syncing and access all features. +
+ You can manage your linked accounts later in Settings → Linked Accounts. +
+
+ +
+ {linkedAccountProviderStates + .sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)) + .map(state => ( + + ))} +
+ {canSkip && ( + + )} +
+
+ ); +}; diff --git a/packages/web/src/ee/features/permissionSyncing/components/linkButton.tsx b/packages/web/src/ee/features/permissionSyncing/components/linkButton.tsx new file mode 100644 index 000000000..757ec715e --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/linkButton.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { Button } from "@/components/ui/button"; +import { Link2 } from "lucide-react"; +import { signIn } from "next-auth/react"; + +interface LinkButtonProps { + provider: string; + callbackUrl: string; +} + +export const LinkButton = ({ provider, callbackUrl }: LinkButtonProps) => { + const handleLink = () => { + signIn(provider, { + redirectTo: callbackUrl + }); + }; + + return ( + + ); +}; diff --git a/packages/web/src/ee/features/permissionSyncing/components/linkedAccountProviderCard.tsx b/packages/web/src/ee/features/permissionSyncing/components/linkedAccountProviderCard.tsx new file mode 100644 index 000000000..766784dde --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/linkedAccountProviderCard.tsx @@ -0,0 +1,100 @@ +import { getAuthProviderInfo } from "@/lib/utils"; +import { Check, X, AlertCircle } from "lucide-react"; +import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { ProviderIcon } from "./providerIcon"; +import { ProviderInfo } from "./providerInfo"; +import { UnlinkButton } from "./unlinkButton"; +import { LinkButton } from "./linkButton"; +import { LinkedAccountProviderState } from "@/ee/features/permissionSyncing/types" +import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"; + +interface LinkedAccountProviderCardProps { + linkedAccountProviderState: LinkedAccountProviderState; + callbackUrl?: string; +} + +export function LinkedAccountProviderCard({ + linkedAccountProviderState, + callbackUrl, +}: LinkedAccountProviderCardProps) { + const providerInfo = getAuthProviderInfo(linkedAccountProviderState.id); + const defaultCallbackUrl = `/${SINGLE_TENANT_ORG_DOMAIN}/settings/permission-syncing`; + + return ( + + +
+
+
+ +
+
+ + + + +
+ {linkedAccountProviderState.isLinked? ( +
+
+ + + Connected + +
+ {linkedAccountProviderState.linkedAccountId && ( + <> + + + {linkedAccountProviderState.linkedAccountId} + + + )} +
+ ) : ( +
+ + + Not connected + +
+ )} + {linkedAccountProviderState.error && ( +
+ + + Token refresh failed - please reconnect + +
+ )} +
+
+
+
+
+ {linkedAccountProviderState.isLinked? ( + + ) : ( + + )} +
+
+
+
+ ); +} + diff --git a/packages/web/src/ee/features/permissionSyncing/components/linkedAccountsSettings.tsx b/packages/web/src/ee/features/permissionSyncing/components/linkedAccountsSettings.tsx new file mode 100644 index 000000000..8b326bdfd --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/linkedAccountsSettings.tsx @@ -0,0 +1,61 @@ +import { ShieldCheck } from "lucide-react"; +import { getLinkedAccountProviderStates } from "@/ee/features/permissionSyncing/actions" +import { Card, CardContent } from "@/components/ui/card"; +import { LinkedAccountProviderCard } from "./linkedAccountProviderCard"; +import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; +import { isServiceError } from "@/lib/utils"; + +export async function LinkedAccountsSettings() { + const linkedAccountProviderStates = await getLinkedAccountProviderStates(); + if (isServiceError(linkedAccountProviderStates)) { + return
+ +
+

An error occurred

+

+ {typeof linkedAccountProviderStates.message === 'string' + ? linkedAccountProviderStates.message + : "A server error occurred while checking your account status. Please try again or contact support."} +

+
+
+ } + + return ( +
+
+

Linked Accounts

+

+ Manage your linked account integrations for permission syncing. +

+
+ + {linkedAccountProviderStates.length === 0 ? ( + + +
+ +
+

No integration providers configured

+

+ Contact your administrator to configure integration providers for your organization. +

+
+
+ ) : ( +
+ {linkedAccountProviderStates + .sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)) + .map((state) => { + return ( + + ); + })} +
+ )} +
+ ); +} diff --git a/packages/web/src/ee/features/permissionSyncing/components/providerBadge.tsx b/packages/web/src/ee/features/permissionSyncing/components/providerBadge.tsx new file mode 100644 index 000000000..522da946a --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/providerBadge.tsx @@ -0,0 +1,16 @@ +import { Badge } from "@/components/ui/badge"; + +interface ProviderBadgeProps { + required: boolean; +} + +export function ProviderBadge({ required }: ProviderBadgeProps) { + return ( + + {required ? "Required" : "Optional"} + + ); +} diff --git a/packages/web/src/ee/features/permissionSyncing/components/providerIcon.tsx b/packages/web/src/ee/features/permissionSyncing/components/providerIcon.tsx new file mode 100644 index 000000000..fa4394738 --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/providerIcon.tsx @@ -0,0 +1,57 @@ +import Image from "next/image"; +import { ShieldCheck } from "lucide-react"; + +interface ProviderIconProps { + icon?: { + src: string; + className?: string; + } | null; + displayName: string; + size?: "sm" | "md" | "lg"; +} + +const sizeClasses = { + sm: { + container: "h-8 w-8", + icon: "h-4 w-4" + }, + md: { + container: "h-10 w-10", + icon: "h-5 w-5" + }, + lg: { + container: "h-12 w-12", + icon: "h-6 w-6" + } +}; + +const sizeDimensions = { + sm: { width: 16, height: 16 }, + md: { width: 20, height: 20 }, + lg: { width: 24, height: 24 } +}; + +export function ProviderIcon({ icon, displayName, size = "md" }: ProviderIconProps) { + const sizes = sizeClasses[size]; + const dimensions = sizeDimensions[size]; + + if (icon) { + return ( +
+ {displayName} +
+ ); + } + + return ( +
+ +
+ ); +} diff --git a/packages/web/src/ee/features/permissionSyncing/components/providerInfo.tsx b/packages/web/src/ee/features/permissionSyncing/components/providerInfo.tsx new file mode 100644 index 000000000..3d1d83393 --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/providerInfo.tsx @@ -0,0 +1,23 @@ +import { getAuthProviderInfo } from "@/lib/utils"; +import { ProviderBadge } from "./providerBadge"; + +interface ProviderInfoProps { + providerId: string; + required: boolean; + showBadge?: boolean; +} + +export function ProviderInfo({ providerId, required, showBadge = true }: ProviderInfoProps) { + const providerInfo = getAuthProviderInfo(providerId); + + return ( + <> +
+ + {providerInfo.displayName} + + {showBadge && } +
+ + ); +} diff --git a/packages/web/src/ee/features/permissionSyncing/components/unlinkButton.tsx b/packages/web/src/ee/features/permissionSyncing/components/unlinkButton.tsx new file mode 100644 index 000000000..ebd05ab9c --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/components/unlinkButton.tsx @@ -0,0 +1,75 @@ +'use client'; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Unlink, Loader2 } from "lucide-react"; +import { unlinkLinkedAccountProvider } from "../actions"; +import { isServiceError } from "@/lib/utils"; +import { useRouter } from "next/navigation"; +import { useToast } from "@/components/hooks/use-toast"; + +interface UnlinkButtonProps { + provider: string; + providerName: string; +} + +export const UnlinkButton = ({ provider, providerName }: UnlinkButtonProps) => { + const [isUnlinking, setIsUnlinking] = useState(false); + const router = useRouter(); + const { toast } = useToast(); + + const handleUnlink = async () => { + if (!confirm(`Are you sure you want to disconnect your ${providerName} account?`)) { + return; + } + + setIsUnlinking(true); + try { + const result = await unlinkLinkedAccountProvider(provider); + + if (isServiceError(result)) { + toast({ + description: `❌ Failed to disconnect account. ${result.message}`, + variant: "destructive", + }); + setIsUnlinking(false); + return; + } + + toast({ + description: `✅ ${providerName} account disconnected successfully.`, + }); + + // Refresh the page to show updated state + router.refresh(); + } catch (error) { + toast({ + description: `❌ Failed to disconnect account. ${error instanceof Error ? error.message : "Unknown error"}`, + variant: "destructive", + }); + setIsUnlinking(false); + } + }; + + return ( + + ); +}; diff --git a/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts b/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts new file mode 100644 index 000000000..f6ebef067 --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts @@ -0,0 +1,165 @@ +import { loadConfig } from "@sourcebot/shared"; +import { env } from "@/env.mjs"; +import { createLogger } from "@sourcebot/logger"; +import { getTokenFromConfig } from '@sourcebot/crypto'; +import { GitHubIdentityProviderConfig, GitLabIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type"; +import { LinkedAccountTokensMap } from "@/auth" +const { prisma } = await import('@/prisma'); + +const logger = createLogger('web-ee-token-refresh'); + +export async function refreshLinkedAccountTokens( + currentTokens: LinkedAccountTokensMap | undefined +): Promise { + if (!currentTokens) { + return {}; + } + + const now = Math.floor(Date.now() / 1000); + const bufferTimeS = 5 * 60; // 5 minutes + + const updatedTokens: LinkedAccountTokensMap = { ...currentTokens }; + + await Promise.all( + Object.entries(currentTokens).map(async ([providerAccountId, tokenData]) => { + const provider = tokenData.provider; + if (provider !== 'github' && provider !== 'gitlab') { + return; + } + + if (tokenData.expiresAt && now >= (tokenData.expiresAt - bufferTimeS)) { + try { + logger.info(`Refreshing token for providerAccountId: ${providerAccountId} (${tokenData.provider})`); + const refreshedTokens = await refreshOAuthToken( + provider, + tokenData.refreshToken + ); + + if (refreshedTokens) { + await prisma.account.update({ + where: { + provider_providerAccountId: { + provider: provider, + providerAccountId: providerAccountId + } + }, + data: { + access_token: refreshedTokens.accessToken, + refresh_token: refreshedTokens.refreshToken, + expires_at: refreshedTokens.expiresAt, + }, + }); + + updatedTokens[providerAccountId] = { + provider: tokenData.provider, + accessToken: refreshedTokens.accessToken, + refreshToken: refreshedTokens.refreshToken ?? tokenData.refreshToken, + expiresAt: refreshedTokens.expiresAt, + }; + logger.info(`Successfully refreshed token for provider: ${provider}`); + } else { + logger.error(`Failed to refresh token for provider: ${provider}`); + updatedTokens[providerAccountId] = { + ...tokenData, + error: 'RefreshTokenError', + }; + } + } catch (error) { + logger.error(`Error refreshing token for provider ${provider}:`, error); + updatedTokens[providerAccountId] = { + ...tokenData, + error: 'RefreshTokenError', + }; + } + } + }) + ); + + return updatedTokens; +} + +export async function refreshOAuthToken( + provider: string, + refreshToken: string, +): Promise<{ accessToken: string; refreshToken: string | null; expiresAt: number } | null> { + try { + const config = await loadConfig(env.CONFIG_PATH); + const identityProviders = config?.identityProviders ?? []; + + const providerConfigs = identityProviders.filter(idp => idp.provider === provider); + if (providerConfigs.length === 0) { + logger.error(`Provider config not found or invalid for: ${provider}`); + return null; + } + + // Loop through all provider configs and return on first successful fetch + // + // The reason we have to do this is because 1) we might have multiple providers of the same type (ex. we're connecting to multiple gitlab instances) and 2) there isn't + // a trivial way to map a provider config to the associated Account object in the DB. The reason the config is involved at all here is because we need the client + // id/secret in order to refresh the token, and that info is in the config. We could in theory bypass this by storing the client id/secret for the provider in the + // Account table but we decided not to do that since these are secret. Instead, we simply try all of the client/id secrets for the associated provider type. This is safe + // to do because only the correct client id/secret will work since we're using a specific refresh token. + for (const providerConfig of providerConfigs) { + try { + // Get client credentials from config + const linkedAccountProviderConfig = providerConfig as GitHubIdentityProviderConfig | GitLabIdentityProviderConfig + const clientId = await getTokenFromConfig(linkedAccountProviderConfig.clientId); + const clientSecret = await getTokenFromConfig(linkedAccountProviderConfig.clientSecret); + const baseUrl = linkedAccountProviderConfig.baseUrl + + let url: string; + if (baseUrl) { + url = provider === 'github' + ? `${baseUrl}/login/oauth/access_token` + : `${baseUrl}/oauth/token`; + } else if (provider === 'github') { + url = 'https://github.com/login/oauth/access_token'; + } else if (provider === 'gitlab') { + url = 'https://gitlab.com/oauth/token'; + } else { + logger.error(`Unsupported provider for token refresh: ${provider}`); + continue; + } + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json', + }, + body: new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + grant_type: 'refresh_token', + refresh_token: refreshToken, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + logger.debug(`Failed to refresh ${provider} token with config: ${response.status} ${errorText}`); + continue; + } + + const data = await response.json(); + + const result = { + accessToken: data.access_token, + refreshToken: data.refresh_token ?? null, + expiresAt: data.expires_in ? Math.floor(Date.now() / 1000) + data.expires_in : 0, + }; + + return result; + } catch (configError) { + logger.debug(`Error trying provider config for ${provider}:`, configError); + continue; + } + } + + logger.error(`All provider configs failed for: ${provider}`); + return null; + } catch (error) { + logger.error(`Error refreshing ${provider} token:`, error); + return null; + } +} diff --git a/packages/web/src/ee/features/permissionSyncing/types.ts b/packages/web/src/ee/features/permissionSyncing/types.ts new file mode 100644 index 000000000..3fdc16e26 --- /dev/null +++ b/packages/web/src/ee/features/permissionSyncing/types.ts @@ -0,0 +1,7 @@ +export type LinkedAccountProviderState = { + id: string; + required: boolean; + isLinked: boolean; + linkedAccountId?: string; + error?: string; +}; \ No newline at end of file diff --git a/packages/web/src/ee/features/sso/sso.ts b/packages/web/src/ee/features/sso/sso.ts index 7accc0658..9a5153ff7 100644 --- a/packages/web/src/ee/features/sso/sso.ts +++ b/packages/web/src/ee/features/sso/sso.ts @@ -1,4 +1,3 @@ -import type { Provider } from "next-auth/providers"; import { env } from "@/env.mjs"; import GitHub from "next-auth/providers/github"; import Google from "next-auth/providers/google"; @@ -10,174 +9,266 @@ import { prisma } from "@/prisma"; import { OAuth2Client } from "google-auth-library"; import Credentials from "next-auth/providers/credentials"; import type { User as AuthJsUser } from "next-auth"; +import type { Provider } from "next-auth/providers"; import { onCreateUser } from "@/lib/authUtils"; import { createLogger } from "@sourcebot/logger"; -import { hasEntitlement } from "@sourcebot/shared"; +import { hasEntitlement, loadConfig } from "@sourcebot/shared"; +import { getTokenFromConfig } from "@sourcebot/crypto"; +import type { IdentityProvider } from "@/auth"; +import { GCPIAPIdentityProviderConfig, GitHubIdentityProviderConfig, GitLabIdentityProviderConfig, GoogleIdentityProviderConfig, KeycloakIdentityProviderConfig, MicrosoftEntraIDIdentityProviderConfig, OktaIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type"; const logger = createLogger('web-sso'); -export const getSSOProviders = (): Provider[] => { - const providers: Provider[] = []; +const GITHUB_CLOUD_HOSTNAME = "github.com" - if (env.AUTH_EE_GITHUB_CLIENT_ID && env.AUTH_EE_GITHUB_CLIENT_SECRET) { - providers.push(GitHub({ - clientId: env.AUTH_EE_GITHUB_CLIENT_ID, - clientSecret: env.AUTH_EE_GITHUB_CLIENT_SECRET, - enterprise: { - baseUrl: env.AUTH_EE_GITHUB_BASE_URL, - }, - authorization: { - params: { - scope: [ - 'read:user', - 'user:email', - // Permission syncing requires the `repo` scope in order to fetch repositories - // for the authenticated user. - // @see: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user - ...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ? - ['repo'] : - [] - ), - ].join(' '), - }, - }, - })); +export const getEEIdentityProviders = async (): Promise => { + const providers: IdentityProvider[] = []; + + const config = await loadConfig(env.CONFIG_PATH); + const identityProviders = config?.identityProviders ?? []; + + for (const identityProvider of identityProviders) { + if (identityProvider.provider === "github") { + const providerConfig = identityProvider as GitHubIdentityProviderConfig; + const clientId = await getTokenFromConfig(providerConfig.clientId); + const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); + const baseUrl = providerConfig.baseUrl; + providers.push({ provider: createGitHubProvider(clientId, clientSecret, baseUrl), purpose: providerConfig.purpose, required: providerConfig.accountLinkingRequired ?? false}); + } + if (identityProvider.provider === "gitlab") { + const providerConfig = identityProvider as GitLabIdentityProviderConfig; + const clientId = await getTokenFromConfig(providerConfig.clientId); + const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); + const baseUrl = providerConfig.baseUrl; + providers.push({ provider: createGitLabProvider(clientId, clientSecret, baseUrl), purpose: providerConfig.purpose, required: providerConfig.accountLinkingRequired ?? false}); + } + if (identityProvider.provider === "google") { + const providerConfig = identityProvider as GoogleIdentityProviderConfig; + const clientId = await getTokenFromConfig(providerConfig.clientId); + const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); + providers.push({ provider: createGoogleProvider(clientId, clientSecret), purpose: providerConfig.purpose}); + } + if (identityProvider.provider === "okta") { + const providerConfig = identityProvider as OktaIdentityProviderConfig; + const clientId = await getTokenFromConfig(providerConfig.clientId); + const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); + const issuer = await getTokenFromConfig(providerConfig.issuer); + providers.push({ provider: createOktaProvider(clientId, clientSecret, issuer), purpose: providerConfig.purpose}); + } + if (identityProvider.provider === "keycloak") { + const providerConfig = identityProvider as KeycloakIdentityProviderConfig; + const clientId = await getTokenFromConfig(providerConfig.clientId); + const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); + const issuer = await getTokenFromConfig(providerConfig.issuer); + providers.push({ provider: createKeycloakProvider(clientId, clientSecret, issuer), purpose: providerConfig.purpose }); + } + if (identityProvider.provider === "microsoft-entra-id") { + const providerConfig = identityProvider as MicrosoftEntraIDIdentityProviderConfig; + const clientId = await getTokenFromConfig(providerConfig.clientId); + const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); + const issuer = await getTokenFromConfig(providerConfig.issuer); + providers.push({ provider: createMicrosoftEntraIDProvider(clientId, clientSecret, issuer), purpose: providerConfig.purpose }); + } + if (identityProvider.provider === "gcp-iap") { + const providerConfig = identityProvider as GCPIAPIdentityProviderConfig; + const audience = await getTokenFromConfig(providerConfig.audience); + providers.push({ provider: createGCPIAPProvider(audience), purpose: providerConfig.purpose }); + } } - if (env.AUTH_EE_GITLAB_CLIENT_ID && env.AUTH_EE_GITLAB_CLIENT_SECRET) { - providers.push(Gitlab({ - clientId: env.AUTH_EE_GITLAB_CLIENT_ID, - clientSecret: env.AUTH_EE_GITLAB_CLIENT_SECRET, - authorization: { - url: `${env.AUTH_EE_GITLAB_BASE_URL}/oauth/authorize`, - params: { - scope: [ - "read_user", - // Permission syncing requires the `read_api` scope in order to fetch projects - // for the authenticated user and project members. - // @see: https://docs.gitlab.com/ee/api/projects.html#list-all-projects - ...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ? - ['read_api'] : - [] - ), - ].join(' '), - }, - }, - token: { - url: `${env.AUTH_EE_GITLAB_BASE_URL}/oauth/token`, + // @deprecate in favor of defining identity providers throught the identityProvider object in the config file. This was done to allow for more control over + // which identity providers are defined and their purpose. We've left this logic here to support backwards compat with deployments that expect these env vars, + // but this logic will be removed in the future + // We only go through this path if no identityProviders are defined in the config to prevent accidental duplication of providers + if (identityProviders.length == 0) { + if (env.AUTH_EE_GITHUB_CLIENT_ID && env.AUTH_EE_GITHUB_CLIENT_SECRET) { + providers.push({ provider: createGitHubProvider(env.AUTH_EE_GITHUB_CLIENT_ID, env.AUTH_EE_GITHUB_CLIENT_SECRET, env.AUTH_EE_GITHUB_BASE_URL), purpose: "sso" }); + } + + if (env.AUTH_EE_GITLAB_CLIENT_ID && env.AUTH_EE_GITLAB_CLIENT_SECRET) { + providers.push({ provider: createGitLabProvider(env.AUTH_EE_GITLAB_CLIENT_ID, env.AUTH_EE_GITLAB_CLIENT_SECRET, env.AUTH_EE_GITLAB_BASE_URL), purpose: "sso" }); + } + + if (env.AUTH_EE_GOOGLE_CLIENT_ID && env.AUTH_EE_GOOGLE_CLIENT_SECRET) { + providers.push({ provider: createGoogleProvider(env.AUTH_EE_GOOGLE_CLIENT_ID, env.AUTH_EE_GOOGLE_CLIENT_SECRET), purpose: "sso" }); + } + + if (env.AUTH_EE_OKTA_CLIENT_ID && env.AUTH_EE_OKTA_CLIENT_SECRET && env.AUTH_EE_OKTA_ISSUER) { + providers.push({ provider: createOktaProvider(env.AUTH_EE_OKTA_CLIENT_ID, env.AUTH_EE_OKTA_CLIENT_SECRET, env.AUTH_EE_OKTA_ISSUER), purpose: "sso" }); + } + + if (env.AUTH_EE_KEYCLOAK_CLIENT_ID && env.AUTH_EE_KEYCLOAK_CLIENT_SECRET && env.AUTH_EE_KEYCLOAK_ISSUER) { + providers.push({ provider: createKeycloakProvider(env.AUTH_EE_KEYCLOAK_CLIENT_ID, env.AUTH_EE_KEYCLOAK_CLIENT_SECRET, env.AUTH_EE_KEYCLOAK_ISSUER), purpose: "sso" }); + } + + if (env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID && env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET && env.AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER) { + providers.push({ provider: createMicrosoftEntraIDProvider(env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID, env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET, env.AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER), purpose: "sso" }); + } + + if (env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) { + providers.push({ provider: createGCPIAPProvider(env.AUTH_EE_GCP_IAP_AUDIENCE), purpose: "sso" }); + } + } + + return providers; +} + +const createGitHubProvider = (clientId: string, clientSecret: string, baseUrl?: string): Provider => { + const hostname = baseUrl ? new URL(baseUrl).hostname : GITHUB_CLOUD_HOSTNAME + return GitHub({ + clientId: clientId, + clientSecret: clientSecret, + ...(hostname === GITHUB_CLOUD_HOSTNAME ? { enterprise: { baseUrl: baseUrl } } : {}), // if this is set the provider expects GHE so we need this check + authorization: { + params: { + scope: [ + 'read:user', + 'user:email', + // Permission syncing requires the `repo` scope in order to fetch repositories + // for the authenticated user. + // @see: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user + ...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ? + ['repo'] : + [] + ), + ].join(' '), }, - userinfo: { - url: `${env.AUTH_EE_GITLAB_BASE_URL}/api/v4/user`, + }, + }); +} + +const createGitLabProvider = (clientId: string, clientSecret: string, baseUrl?: string): Provider => { + const url = baseUrl ?? 'https://gitlab.com'; + return Gitlab({ + clientId: clientId, + clientSecret: clientSecret, + authorization: { + url: `${url}/oauth/authorize`, + params: { + scope: [ + "read_user", + // Permission syncing requires the `read_api` scope in order to fetch projects + // for the authenticated user and project members. + // @see: https://docs.gitlab.com/ee/api/projects.html#list-all-projects + ...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ? + ['read_api'] : + [] + ), + ].join(' '), }, - })); - } + }, + token: { + url: `${url}/oauth/token`, + }, + userinfo: { + url: `${url}/api/v4/user`, + }, + }); +} - if (env.AUTH_EE_GOOGLE_CLIENT_ID && env.AUTH_EE_GOOGLE_CLIENT_SECRET) { - providers.push(Google({ - clientId: env.AUTH_EE_GOOGLE_CLIENT_ID, - clientSecret: env.AUTH_EE_GOOGLE_CLIENT_SECRET, - })); - } +const createGoogleProvider = (clientId: string, clientSecret: string): Provider => { + return Google({ + clientId: clientId, + clientSecret: clientSecret, + }); +} - if (env.AUTH_EE_OKTA_CLIENT_ID && env.AUTH_EE_OKTA_CLIENT_SECRET && env.AUTH_EE_OKTA_ISSUER) { - providers.push(Okta({ - clientId: env.AUTH_EE_OKTA_CLIENT_ID, - clientSecret: env.AUTH_EE_OKTA_CLIENT_SECRET, - issuer: env.AUTH_EE_OKTA_ISSUER, - })); - } +const createOktaProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { + return Okta({ + clientId: clientId, + clientSecret: clientSecret, + issuer: issuer, + }); +} - if (env.AUTH_EE_KEYCLOAK_CLIENT_ID && env.AUTH_EE_KEYCLOAK_CLIENT_SECRET && env.AUTH_EE_KEYCLOAK_ISSUER) { - providers.push(Keycloak({ - clientId: env.AUTH_EE_KEYCLOAK_CLIENT_ID, - clientSecret: env.AUTH_EE_KEYCLOAK_CLIENT_SECRET, - issuer: env.AUTH_EE_KEYCLOAK_ISSUER, - })); - } +const createKeycloakProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { + return Keycloak({ + clientId: clientId, + clientSecret: clientSecret, + issuer: issuer, + }); +} - if (env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID && env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET && env.AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER) { - providers.push(MicrosoftEntraID({ - clientId: env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID, - clientSecret: env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET, - issuer: env.AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER, - })); - } +const createMicrosoftEntraIDProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { + return MicrosoftEntraID({ + clientId: clientId, + clientSecret: clientSecret, + issuer: issuer, + }); +} - if (env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) { - providers.push(Credentials({ - id: "gcp-iap", - name: "Google Cloud IAP", - credentials: {}, - authorize: async (credentials, req) => { - try { - const iapAssertion = req.headers?.get("x-goog-iap-jwt-assertion"); - if (!iapAssertion || typeof iapAssertion !== "string") { - logger.warn("No IAP assertion found in headers"); - return null; - } - - const oauth2Client = new OAuth2Client(); - - const { pubkeys } = await oauth2Client.getIapPublicKeys(); - const ticket = await oauth2Client.verifySignedJwtWithCertsAsync( - iapAssertion, - pubkeys, - env.AUTH_EE_GCP_IAP_AUDIENCE, - ['https://cloud.google.com/iap'] - ); - - const payload = ticket.getPayload(); - if (!payload) { - logger.warn("Invalid IAP token payload"); - return null; - } - - const email = payload.email; - const name = payload.name || payload.email; - const image = payload.picture; - - if (!email) { - logger.warn("Missing email in IAP token"); - return null; - } - - const existingUser = await prisma.user.findUnique({ - where: { email } - }); +const createGCPIAPProvider = (audience: string): Provider => { + return Credentials({ + id: "gcp-iap", + name: "Google Cloud IAP", + credentials: {}, + authorize: async (credentials, req) => { + try { + const iapAssertion = req.headers?.get("x-goog-iap-jwt-assertion"); + if (!iapAssertion || typeof iapAssertion !== "string") { + logger.warn("No IAP assertion found in headers"); + return null; + } + + const oauth2Client = new OAuth2Client(); + + const { pubkeys } = await oauth2Client.getIapPublicKeys(); + const ticket = await oauth2Client.verifySignedJwtWithCertsAsync( + iapAssertion, + pubkeys, + audience, + ['https://cloud.google.com/iap'] + ); - if (!existingUser) { - const newUser = await prisma.user.create({ - data: { - email, - name, - image, - } - }); - - const authJsUser: AuthJsUser = { - id: newUser.id, - email: newUser.email, - name: newUser.name, - image: newUser.image, - }; - - await onCreateUser({ user: authJsUser }); - return authJsUser; - } else { - return { - id: existingUser.id, - email: existingUser.email, - name: existingUser.name, - image: existingUser.image, - }; - } - } catch (error) { - logger.error("Error verifying IAP token:", error); + const payload = ticket.getPayload(); + if (!payload) { + logger.warn("Invalid IAP token payload"); return null; } - }, - })); - } - return providers; + const email = payload.email; + const name = payload.name || payload.email; + const image = payload.picture; + + if (!email) { + logger.warn("Missing email in IAP token"); + return null; + } + + const existingUser = await prisma.user.findUnique({ + where: { email } + }); + + if (!existingUser) { + const newUser = await prisma.user.create({ + data: { + email, + name, + image, + } + }); + + const authJsUser: AuthJsUser = { + id: newUser.id, + email: newUser.email, + name: newUser.name, + image: newUser.image, + }; + + await onCreateUser({ user: authJsUser }); + return authJsUser; + } else { + return { + id: existingUser.id, + email: existingUser.email, + name: existingUser.name, + image: existingUser.image, + }; + } + } catch (error) { + logger.error("Error verifying IAP token:", error); + return null; + } + }, + }); } \ No newline at end of file diff --git a/packages/web/src/features/chat/actions.ts b/packages/web/src/features/chat/actions.ts index ad4e9f123..19ec9abd5 100644 --- a/packages/web/src/features/chat/actions.ts +++ b/packages/web/src/features/chat/actions.ts @@ -22,15 +22,18 @@ import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { getTokenFromConfig } from "@sourcebot/crypto"; import { ChatVisibility, OrgRole, Prisma } from "@sourcebot/db"; +import { createLogger } from "@sourcebot/logger"; import { LanguageModel } from "@sourcebot/schemas/v3/languageModel.type"; import { Token } from "@sourcebot/schemas/v3/shared.type"; -import { loadConfig } from "@sourcebot/shared"; import { generateText, JSONValue, extractReasoningMiddleware, wrapLanguageModel } from "ai"; +import { loadConfig } from "@sourcebot/shared"; import fs from 'fs'; import { StatusCodes } from "http-status-codes"; import path from 'path'; import { LanguageModelInfo, SBChatMessage } from "./types"; +const logger = createLogger('chat-actions'); + export const createChat = async (domain: string) => sew(() => withAuth((userId) => withOrgMembership(userId, domain, async ({ org }) => { @@ -189,7 +192,7 @@ export const updateChatName = async ({ chatId, name }: { chatId: string, name: s export const generateAndUpdateChatNameFromMessage = async ({ chatId, languageModelId, message }: { chatId: string, languageModelId: string, message: string }, domain: string) => sew(() => withAuth((userId) => - withOrgMembership(userId, domain, async ({ org }) => { + withOrgMembership(userId, domain, async () => { // From the language model ID, attempt to find the // corresponding config in `config.json`. const languageModelConfig = @@ -355,25 +358,20 @@ export const getConfiguredLanguageModelsInfo = async (): Promise => { - if (!env.CONFIG_PATH) { - return []; - } - try { const config = await loadConfig(env.CONFIG_PATH); return config.models ?? []; } catch (error) { - console.error(`Failed to load config file ${env.CONFIG_PATH}: ${error}`); + logger.error('Failed to load language model configuration', error); return []; } } - export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel): Promise<{ model: AISDKLanguageModelV2, providerOptions?: Record>, diff --git a/packages/web/src/initialize.ts b/packages/web/src/initialize.ts index 63eb6a473..b08685b2b 100644 --- a/packages/web/src/initialize.ts +++ b/packages/web/src/initialize.ts @@ -65,30 +65,28 @@ const initSingleTenancy = async () => { } // Sync anonymous access config from the config file - if (env.CONFIG_PATH) { - const config = await loadConfig(env.CONFIG_PATH); - const forceEnableAnonymousAccess = config.settings?.enablePublicAccess ?? env.FORCE_ENABLE_ANONYMOUS_ACCESS === 'true'; + const config = await loadConfig(env.CONFIG_PATH); + const forceEnableAnonymousAccess = config.settings?.enablePublicAccess ?? env.FORCE_ENABLE_ANONYMOUS_ACCESS === 'true'; - if (forceEnableAnonymousAccess) { - if (!hasAnonymousAccessEntitlement) { - logger.warn(`FORCE_ENABLE_ANONYMOUS_ACCESS env var is set to true but anonymous access entitlement is not available. Setting will be ignored.`); - } else { - const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN); - if (org) { - const currentMetadata = getOrgMetadata(org); - const mergedMetadata = { - ...(currentMetadata ?? {}), - anonymousAccessEnabled: true, - }; + if (forceEnableAnonymousAccess) { + if (!hasAnonymousAccessEntitlement) { + logger.warn(`FORCE_ENABLE_ANONYMOUS_ACCESS env var is set to true but anonymous access entitlement is not available. Setting will be ignored.`); + } else { + const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN); + if (org) { + const currentMetadata = getOrgMetadata(org); + const mergedMetadata = { + ...(currentMetadata ?? {}), + anonymousAccessEnabled: true, + }; - await prisma.org.update({ - where: { id: org.id }, - data: { - metadata: mergedMetadata, - }, - }); - logger.info(`Anonymous access enabled via FORCE_ENABLE_ANONYMOUS_ACCESS environment variable`); - } + await prisma.org.update({ + where: { id: org.id }, + data: { + metadata: mergedMetadata, + }, + }); + logger.info(`Anonymous access enabled via FORCE_ENABLE_ANONYMOUS_ACCESS environment variable`); } } } diff --git a/packages/web/src/lib/authProviders.ts b/packages/web/src/lib/authProviders.ts deleted file mode 100644 index ca2a66971..000000000 --- a/packages/web/src/lib/authProviders.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { getProviders } from "@/auth"; - -export interface AuthProvider { - id: string; - name: string; -} - -export const getAuthProviders = (): AuthProvider[] => { - const providers = getProviders(); - return providers.map((provider) => { - if (typeof provider === "function") { - const providerInfo = provider(); - return { id: providerInfo.id, name: providerInfo.name }; - } else { - return { id: provider.id, name: provider.name }; - } - }); -}; \ No newline at end of file diff --git a/packages/web/src/lib/constants.ts b/packages/web/src/lib/constants.ts index 3cd6e2588..21bb97d54 100644 --- a/packages/web/src/lib/constants.ts +++ b/packages/web/src/lib/constants.ts @@ -24,6 +24,7 @@ export const TEAM_FEATURES = [ export const MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME = 'sb.mobile-unsupported-splash-screen-dismissed'; export const AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME = 'sb.agentic-search-tutorial-dismissed'; +export const OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME = 'sb.optional-providers-link-skipped'; // NOTE: changing SOURCEBOT_GUEST_USER_ID may break backwards compatibility since this value is used // to detect old guest users in the DB. If you change this value ensure it doesn't break upgrade flows diff --git a/packages/web/src/lib/identityProviders.ts b/packages/web/src/lib/identityProviders.ts new file mode 100644 index 000000000..dff90187d --- /dev/null +++ b/packages/web/src/lib/identityProviders.ts @@ -0,0 +1,30 @@ +import { getProviders } from "@/auth"; + +export interface IdentityProviderMetadata { + id: string; + name: string; + purpose: "sso" | "account_linking"; + required: boolean; +} + +export const getIdentityProviderMetadata = (): IdentityProviderMetadata[] => { + const providers = getProviders(); + return providers.map((provider) => { + if (typeof provider.provider === "function") { + const providerInfo = provider.provider(); + return { + id: providerInfo.id, + name: providerInfo.name, + purpose: provider.purpose, + required: provider.required ?? false, + }; + } else { + return { + id: provider.provider.id, + name: provider.provider.name, + purpose: provider.purpose, + required: provider.required ?? false, + }; + } + }); +}; \ No newline at end of file diff --git a/schemas/v3/app.json b/schemas/v3/app.json index 2fadfef4a..c6923cd2a 100644 --- a/schemas/v3/app.json +++ b/schemas/v3/app.json @@ -6,7 +6,7 @@ "type": "object", "properties": { "type": { - "const": "githubApp", + "const": "github", "description": "GitHub App Configuration" }, "deploymentHostname": { diff --git a/schemas/v3/identityProvider.json b/schemas/v3/identityProvider.json new file mode 100644 index 000000000..de2e0d113 --- /dev/null +++ b/schemas/v3/identityProvider.json @@ -0,0 +1,198 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "purpose": { + "enum": ["sso", "account_linking"] + }, + "clientId": { + "$ref": "./shared.json#/definitions/Token" + }, + "clientSecret": { + "$ref": "./shared.json#/definitions/Token" + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://github.com", + "description": "The URL of the GitHub host. Defaults to https://github.com", + "examples": [ + "https://github.com", + "https://github.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": ["provider", "purpose", "clientId", "clientSecret"] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "purpose": { + "enum": ["sso", "account_linking"] + }, + "clientId": { + "$ref": "./shared.json#/definitions/Token" + }, + "clientSecret": { + "$ref": "./shared.json#/definitions/Token" + }, + "baseUrl": { + "type": "string", + "format": "url", + "default": "https://gitlab.com", + "description": "The URL of the GitLab host. Defaults to https://gitlab.com", + "examples": [ + "https://gitlab.com", + "https://gitlab.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": ["provider", "purpose", "clientId", "clientSecret"] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "$ref": "./shared.json#/definitions/Token" + }, + "clientSecret": { + "$ref": "./shared.json#/definitions/Token" + } + }, + "required": ["provider", "purpose", "clientId", "clientSecret"] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "$ref": "./shared.json#/definitions/Token" + }, + "clientSecret": { + "$ref": "./shared.json#/definitions/Token" + }, + "issuer": { + "$ref": "./shared.json#/definitions/Token" + } + }, + "required": ["provider", "purpose", "clientId", "clientSecret", "issuer"] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "$ref": "./shared.json#/definitions/Token" + }, + "clientSecret": { + "$ref": "./shared.json#/definitions/Token" + }, + "issuer": { + "$ref": "./shared.json#/definitions/Token" + } + }, + "required": ["provider", "purpose", "clientId", "clientSecret", "issuer"] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "purpose": { + "const": "sso" + }, + "clientId": { + "$ref": "./shared.json#/definitions/Token" + }, + "clientSecret": { + "$ref": "./shared.json#/definitions/Token" + }, + "issuer": { + "$ref": "./shared.json#/definitions/Token" + } + }, + "required": ["provider", "purpose", "clientId", "clientSecret", "issuer"] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "purpose": { + "const": "sso" + }, + "audience": { + "$ref": "./shared.json#/definitions/Token" + } + }, + "required": ["provider", "purpose", "audience"] + } + }, + "oneOf": [ + { + "$ref": "#/definitions/GitHubIdentityProviderConfig" + }, + { + "$ref": "#/definitions/GitLabIdentityProviderConfig" + }, + { + "$ref": "#/definitions/GoogleIdentityProviderConfig" + }, + { + "$ref": "#/definitions/OktaIdentityProviderConfig" + }, + { + "$ref": "#/definitions/KeycloakIdentityProviderConfig" + }, + { + "$ref": "#/definitions/MicrosoftEntraIDIdentityProviderConfig" + }, + { + "$ref": "#/definitions/GCPIAPIdentityProviderConfig" + } + ] +} diff --git a/schemas/v3/index.json b/schemas/v3/index.json index 3bb129b1c..392a2eb98 100644 --- a/schemas/v3/index.json +++ b/schemas/v3/index.json @@ -125,6 +125,13 @@ "items": { "$ref": "./app.json" } + }, + "identityProviders": { + "type": "array", + "description": "Defines a collection of identity providers that are available to Sourcebot.", + "items": { + "$ref": "./identityProvider.json" + } } }, "additionalProperties": false