From cc9dcc01d2231f86a90ddbc14fd0c38fdcaba899 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Tue, 5 May 2026 15:52:45 -0700 Subject: [PATCH 1/2] wip --- .../schemas/v3/identityProvider.schema.mdx | 88 + docs/snippets/schemas/v3/index.schema.mdx | 6041 ++++++++++++----- packages/backend/src/api.ts | 6 +- .../backend/src/ee/accountPermissionSyncer.ts | 65 +- .../backend/src/ee/repoPermissionSyncer.ts | 18 +- packages/backend/src/ee/tokenRefresh.ts | 125 +- .../migration.sql | 15 + packages/db/prisma/schema.prisma | 6 +- .../schemas/src/v3/identityProvider.schema.ts | 88 + .../schemas/src/v3/identityProvider.type.ts | 44 + packages/schemas/src/v3/index.schema.ts | 6041 ++++++++++++----- packages/schemas/src/v3/index.type.ts | 54 +- packages/shared/src/constants.ts | 7 +- packages/shared/src/env.server.ts | 49 +- packages/shared/src/index.server.ts | 3 + .../(app)/settings/linked-accounts/page.tsx | 2 +- .../(server)/ee/permissionSyncStatus/api.ts | 4 +- .../src/app/components/authMethodSelector.tsx | 10 +- .../src/app/login/components/loginForm.tsx | 9 +- packages/web/src/auth.ts | 56 +- packages/web/src/ee/features/sso/actions.ts | 41 +- .../sso/components/connectAccountsCard.tsx | 2 +- .../components/linkedAccountProviderCard.tsx | 19 +- packages/web/src/ee/features/sso/sso.ts | 137 +- .../web/src/lib/encryptedPrismaAdapter.ts | 69 +- packages/web/src/lib/identityProviders.ts | 31 +- packages/web/src/lib/utils.ts | 10 +- schemas/v3/identityProvider.json | 44 + schemas/v3/index.json | 24 +- 29 files changed, 9092 insertions(+), 4016 deletions(-) create mode 100644 packages/db/prisma/migrations/20260504230838_add_provider_id_and_type_to_account/migration.sql diff --git a/docs/snippets/schemas/v3/identityProvider.schema.mdx b/docs/snippets/schemas/v3/identityProvider.schema.mdx index 1747c8619..8280622b2 100644 --- a/docs/snippets/schemas/v3/identityProvider.schema.mdx +++ b/docs/snippets/schemas/v3/identityProvider.schema.mdx @@ -11,6 +11,10 @@ "provider": { "const": "github" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." + }, "purpose": { "enum": [ "sso", @@ -107,6 +111,10 @@ "provider": { "const": "gitlab" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." + }, "purpose": { "enum": [ "sso", @@ -203,6 +211,10 @@ "provider": { "const": "google" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google'." + }, "purpose": { "const": "sso" }, @@ -281,6 +293,10 @@ "provider": { "const": "okta" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Okta'." + }, "purpose": { "const": "sso" }, @@ -390,6 +406,10 @@ "provider": { "const": "keycloak" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Keycloak'." + }, "purpose": { "const": "sso" }, @@ -499,6 +519,10 @@ "provider": { "const": "microsoft-entra-id" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'." + }, "purpose": { "const": "sso" }, @@ -608,6 +632,10 @@ "provider": { "const": "gcp-iap" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'." + }, "purpose": { "const": "sso" }, @@ -655,6 +683,10 @@ "provider": { "const": "bitbucket-cloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, "purpose": { "enum": [ "sso", @@ -740,6 +772,10 @@ "provider": { "const": "authentik" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, "purpose": { "const": "sso" }, @@ -849,6 +885,10 @@ "provider": { "const": "jumpcloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, "purpose": { "const": "sso" }, @@ -958,6 +998,10 @@ "provider": { "const": "bitbucket-server" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, "purpose": { "enum": [ "sso", @@ -1054,6 +1098,10 @@ "provider": { "const": "github" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." + }, "purpose": { "enum": [ "sso", @@ -1150,6 +1198,10 @@ "provider": { "const": "gitlab" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." + }, "purpose": { "enum": [ "sso", @@ -1246,6 +1298,10 @@ "provider": { "const": "google" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google'." + }, "purpose": { "const": "sso" }, @@ -1324,6 +1380,10 @@ "provider": { "const": "okta" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Okta'." + }, "purpose": { "const": "sso" }, @@ -1433,6 +1493,10 @@ "provider": { "const": "keycloak" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Keycloak'." + }, "purpose": { "const": "sso" }, @@ -1542,6 +1606,10 @@ "provider": { "const": "microsoft-entra-id" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'." + }, "purpose": { "const": "sso" }, @@ -1651,6 +1719,10 @@ "provider": { "const": "gcp-iap" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'." + }, "purpose": { "const": "sso" }, @@ -1698,6 +1770,10 @@ "provider": { "const": "authentik" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, "purpose": { "const": "sso" }, @@ -1807,6 +1883,10 @@ "provider": { "const": "bitbucket-cloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, "purpose": { "enum": [ "sso", @@ -1892,6 +1972,10 @@ "provider": { "const": "jumpcloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, "purpose": { "const": "sso" }, @@ -2001,6 +2085,10 @@ "provider": { "const": "bitbucket-server" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, "purpose": { "enum": [ "sso", diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 8076ab8e2..cc1cdd36a 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -4956,2098 +4956,4377 @@ } }, "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." - } + "oneOf": [ + { + "type": "object", + "description": "Map of identity providers keyed by a unique id.`.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" }, - "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" - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "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" - } + "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 + } + ] }, - "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." - } + "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": [ - "env" - ], - "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 + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "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." - } + "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 + } + ] }, - "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" - } + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the 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]*$" }, - "required": [ - "env" - ], - "additionalProperties": false + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "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 + "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "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." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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" + ] + }, + "BitbucketCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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 + } + ] }, - "required": [ - "env" - ], - "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 + } + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "AuthentikIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 + } + ] }, - "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." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "JumpCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "BitbucketServerIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "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." - } + "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 + } + ] }, - "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" - } + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "audience" - ] - }, - "BitbucketCloudIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "bitbucket-cloud" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" - ] - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" }, - "required": [ - "env" - ], - "additionalProperties": false + "accountLinkingRequired": { + "type": "boolean", + "default": 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", + "baseUrl" + ] + } }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "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" - } + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "accountLinkingRequired": { - "type": "boolean", - "default": false - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret" - ] - }, - "AuthentikIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "authentik" - }, - "purpose": { - "const": "sso" - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the 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]*$" }, - "required": [ - "env" - ], - "additionalProperties": false + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." }, - "required": [ - "env" - ], - "additionalProperties": false + "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 + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "issuer" - ] - }, - "JumpCloudIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "jumpcloud" - }, - "purpose": { - "const": "sso" - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "issuer" - ] - }, - "BitbucketServerIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "bitbucket-server" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" - ] - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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 + } + ] }, - "required": [ - "env" - ], - "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 + } + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "baseUrl": { - "type": "string", - "description": "The URL of the Bitbucket Server/Data Center host.", - "examples": [ - "https://bitbucket.example.com" - ], - "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" - }, - "accountLinkingRequired": { - "type": "boolean", - "default": false - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "baseUrl" - ] - } - }, - "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." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, + "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 + } + ] }, - "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" - } + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" }, - "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" - ] + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "baseUrl" + ] + } + ] + } }, - { - "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 + "additionalProperties": false + }, + { + "type": "array", + "deprecated": true, + "description": "Deprecated. Use the object form keyed by id instead.", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" }, - { - "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." + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "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." + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] - } - }, - "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." + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] - } - }, - "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 + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" }, - { - "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" + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "audience" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "BitbucketCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "AuthentikIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 } - }, - "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" + "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": [ - "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" ] - } - }, - "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." + "JumpCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 } - }, - "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" + "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": [ - "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" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "BitbucketServerIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" + ] + }, + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" ], - "additionalProperties": false + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "baseUrl" ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + } + }, + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] - } - }, - "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." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] - } - }, - "required": [ - "provider", - "purpose", - "audience" - ] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "authentik" - }, - "purpose": { - "const": "sso" }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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": "bitbucket-cloud" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "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", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" }, - { - "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" + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "audience" ] }, - "accountLinkingRequired": { - "type": "boolean", - "default": false - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret" - ] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "jumpcloud" - }, - "purpose": { - "const": "sso" - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 } - }, - "required": [ - "env" - ], - "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 + } + ] }, - { - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] }, - "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", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" }, - { - "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" + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 } - }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "issuer" - ] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "bitbucket-server" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" - ] - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + ] + }, + "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": [ - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" + ] + }, + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" ], - "additionalProperties": false + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "baseUrl" ] - }, - "baseUrl": { - "type": "string", - "description": "The URL of the Bitbucket Server/Data Center host.", - "examples": [ - "https://bitbucket.example.com" - ], - "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" - }, - "accountLinkingRequired": { - "type": "boolean", - "default": false } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "baseUrl" ] } - ] - } + } + ] } }, "additionalProperties": false diff --git a/packages/backend/src/api.ts b/packages/backend/src/api.ts index fda71ac44..8afc82540 100644 --- a/packages/backend/src/api.ts +++ b/packages/backend/src/api.ts @@ -1,6 +1,6 @@ import { PrismaClient, RepoIndexingJobType } from '@sourcebot/db'; -import { createLogger, env, PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS } from '@sourcebot/shared'; import { hasEntitlement } from './entitlements.js'; +import { createLogger, doesIdpSupportPermissionSyncing, env } from '@sourcebot/shared'; import express, { Request, Response } from 'express'; import 'express-async-errors'; import * as http from "http"; @@ -126,8 +126,8 @@ export class Api { return; } - if (!PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS.includes(account.provider as typeof PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS[number])) { - res.status(400).json({ error: `Provider '${account.provider}' does not support permission syncing.` }); + if (!doesIdpSupportPermissionSyncing(account.providerType)) { + res.status(400).json({ error: `Provider '${account.providerType}' does not support permission syncing.` }); return; } diff --git a/packages/backend/src/ee/accountPermissionSyncer.ts b/packages/backend/src/ee/accountPermissionSyncer.ts index 9b88cd5ce..17e30cc7c 100644 --- a/packages/backend/src/ee/accountPermissionSyncer.ts +++ b/packages/backend/src/ee/accountPermissionSyncer.ts @@ -65,7 +65,7 @@ export class AccountPermissionSyncer { where: { AND: [ { - provider: { + providerType: { in: PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS } }, @@ -182,7 +182,7 @@ export class AccountPermissionSyncer { const config = await loadConfig(env.CONFIG_PATH); - logger.info(`Syncing permissions for ${account.provider} account (id: ${account.id}) for user ${account.user.email}...`); + logger.info(`Syncing permissions for ${account.providerId} account (id: ${account.id}) for user ${account.user.email}...`); // Ensure the OAuth token is fresh, refreshing it if it is expired or near expiry. // Throws and sets Account.tokenRefreshErrorMessage if the refresh fails. @@ -192,15 +192,18 @@ export class AccountPermissionSyncer { const repoIds = await (async () => { const aggregatedRepoIds: Set = new Set(); - if (account.provider === 'github') { - // @hack: we don't have a way of identifying specific identity providers in the config file. - // Instead, we'll use the first connection of type 'github' and hope for the best. - const baseUrl = Array.from(Object.values(config.connections ?? {})) - .find(connection => connection.type === 'github')?.url; + const idpConfig = config.identityProviders ? + config.identityProviders[account.providerId] : + undefined; + if (!idpConfig) { + throw new Error(`Unable to find IDP config in config.json.`); + } + + if (idpConfig.provider === 'github') { const { octokit } = await createOctokitFromToken({ token: accessToken, - url: baseUrl, + url: idpConfig.baseUrl, }); const scopes = await getGitHubOAuthScopesForAuthenticatedUser(octokit, accessToken); @@ -237,20 +240,18 @@ export class AccountPermissionSyncer { external_codeHostType: 'github', external_id: { in: gitHubRepoIds, - } + }, + ...(account.issuerUrl ? { + external_codeHostUrl: account.issuerUrl, + } : {}), } }); repos.forEach(repo => aggregatedRepoIds.add(repo.id)); - } else if (account.provider === 'gitlab') { - // @hack: we don't have a way of identifying specific identity providers in the config file. - // Instead, we'll use the first connection of type 'gitlab' and hope for the best. - const baseUrl = Array.from(Object.values(config.connections ?? {})) - .find(connection => connection.type === 'gitlab')?.url - + } else if (idpConfig.provider === 'gitlab') { const api = await createGitLabFromOAuthToken({ oauthToken: accessToken, - url: baseUrl, + url: idpConfig.baseUrl, }); const scopes = await getGitLabOAuthScopesForAuthenticatedUser(api); @@ -273,12 +274,15 @@ export class AccountPermissionSyncer { external_codeHostType: 'gitlab', external_id: { in: gitLabProjectIds, - } + }, + ...(account.issuerUrl ? { + external_codeHostUrl: account.issuerUrl, + } : {}), } }); repos.forEach(repo => aggregatedRepoIds.add(repo.id)); - } else if (account.provider === 'bitbucket-cloud') { + } else if (idpConfig.provider === 'bitbucket-cloud') { // @note: we don't pass a user here since we want to use a bearer token // for authentication. const client = createBitbucketCloudClient(/* user = */ undefined, accessToken) @@ -290,22 +294,16 @@ export class AccountPermissionSyncer { external_codeHostType: 'bitbucketCloud', external_id: { in: bitbucketRepoUuids, - } + }, + ...(account.issuerUrl ? { + external_codeHostUrl: account.issuerUrl, + } : {}), } }); repos.forEach(repo => aggregatedRepoIds.add(repo.id)); - } else if (account.provider === 'bitbucket-server') { - // @hack: we don't have a way of identifying specific identity providers in the config file. - // Instead, we'll use the first Bitbucket Server connection's URL as the base URL. - const baseUrl = Array.from(Object.values(config.connections ?? {})) - .find(connection => connection.type === 'bitbucket' && connection.deploymentType === 'server')?.url; - - if (!baseUrl) { - throw new Error(`No Bitbucket Server connection URL found in config for account ${account.id}`); - } - - const client = createBitbucketServerClient(baseUrl, /* user = */ undefined, accessToken); + } else if (idpConfig.provider === 'bitbucket-server') { + const client = createBitbucketServerClient(idpConfig.baseUrl, /* user = */ undefined, accessToken); const serverRepos = await getReposForAuthenticatedBitbucketServerUser(client); const serverRepoIds = serverRepos.map(r => r.id); @@ -313,12 +311,15 @@ export class AccountPermissionSyncer { where: { external_codeHostType: 'bitbucketServer', external_id: { in: serverRepoIds }, + ...(account.issuerUrl ? { + external_codeHostUrl: account.issuerUrl, + } : {}), } }); repos.forEach(repo => aggregatedRepoIds.add(repo.id)); } else { - throw new Error(`Unsupported code host type: ${account.provider}`); + throw new Error(`Unsupported provider type: ${idpConfig.provider}`); } return Array.from(aggregatedRepoIds); @@ -371,7 +372,7 @@ export class AccountPermissionSyncer { } }); - logger.info(`Permissions synced for ${account.provider} account (id: ${account.id}) for user ${account.user.email}`); + logger.info(`Permissions synced for ${account.providerId} account (id: ${account.id}) for user ${account.user.email}`); } private async onJobFailed(job: Job | undefined, err: Error) { diff --git a/packages/backend/src/ee/repoPermissionSyncer.ts b/packages/backend/src/ee/repoPermissionSyncer.ts index befb8593e..a9e816c00 100644 --- a/packages/backend/src/ee/repoPermissionSyncer.ts +++ b/packages/backend/src/ee/repoPermissionSyncer.ts @@ -222,10 +222,11 @@ export class RepoPermissionSyncer { const accounts = await this.db.account.findMany({ where: { - provider: 'github', + providerType: 'github', providerAccountId: { in: githubUserIds, - } + }, + issuerUrl: credentials.hostUrl, }, }); @@ -248,10 +249,11 @@ export class RepoPermissionSyncer { const accounts = await this.db.account.findMany({ where: { - provider: 'gitlab', + providerType: 'gitlab', providerAccountId: { in: gitlabUserIds, - } + }, + issuerUrl: credentials.hostUrl, }, }); @@ -289,10 +291,11 @@ export class RepoPermissionSyncer { const accounts = await this.db.account.findMany({ where: { - provider: 'bitbucket-cloud', + providerType: 'bitbucket-cloud', providerAccountId: { in: userAccountIds, - } + }, + issuerUrl: credentials.hostUrl, }, }); @@ -328,8 +331,9 @@ export class RepoPermissionSyncer { const accounts = await this.db.account.findMany({ where: { - provider: 'bitbucket-server', + providerType: 'bitbucket-server', providerAccountId: { in: userIds }, + issuerUrl: credentials.hostUrl, } }); diff --git a/packages/backend/src/ee/tokenRefresh.ts b/packages/backend/src/ee/tokenRefresh.ts index 82f162083..cd97b24c4 100644 --- a/packages/backend/src/ee/tokenRefresh.ts +++ b/packages/backend/src/ee/tokenRefresh.ts @@ -25,10 +25,10 @@ const SUPPORTED_PROVIDERS = [ 'bitbucket-server', ] as const satisfies IdentityProviderType[]; -type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number]; +type SupportedProviderType = (typeof SUPPORTED_PROVIDERS)[number]; -const isSupportedProvider = (provider: string): provider is SupportedProvider => - SUPPORTED_PROVIDERS.includes(provider as SupportedProvider); +const isSupportedProvider = (providerType: string): providerType is SupportedProviderType => + SUPPORTED_PROVIDERS.includes(providerType as SupportedProviderType); // @see: https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 const OAuthTokenResponseSchema = z.object({ @@ -65,10 +65,10 @@ export const ensureFreshAccountToken = async ( db: PrismaClient, ): Promise => { if (!account.access_token) { - throw new Error(`Account ${account.id} (${account.provider}) has no access token.`); + throw new Error(`Account ${account.id} (${account.providerId}) has no access token.`); } - if (!isSupportedProvider(account.provider)) { + if (!isSupportedProvider(account.providerType)) { // Non-refreshable provider — just decrypt and return whatever is stored. const token = decryptOAuthToken(account.access_token); if (!token) { @@ -92,7 +92,7 @@ export const ensureFreshAccountToken = async ( } if (!account.refresh_token) { - const message = `Account ${account.id} (${account.provider}) token is expired and has no refresh token.`; + const message = `Account ${account.id} (${account.providerId}) token is expired and has no refresh token.`; logger.error(message); await setTokenRefreshError(account.id, message, db); throw new Error(message); @@ -100,17 +100,22 @@ export const ensureFreshAccountToken = async ( const refreshToken = decryptOAuthToken(account.refresh_token); if (!refreshToken) { - const message = `Failed to decrypt refresh token for account ${account.id} (${account.provider}).`; + const message = `Failed to decrypt refresh token for account ${account.id} (${account.providerId}).`; logger.error(message); await setTokenRefreshError(account.id, message, db); throw new Error(message); } - logger.debug(`Refreshing OAuth token for account ${account.id} (${account.provider})...`); + logger.debug(`Refreshing OAuth token for account ${account.id} (${account.providerId})...`); + + const refreshResponse = await refreshOAuthToken( + account.providerId, + account.providerType, + refreshToken + ); - const refreshResponse = await refreshOAuthToken(account.provider, refreshToken); if (!refreshResponse) { - const message = `OAuth token refresh failed for account ${account.id} (${account.provider}).`; + const message = `OAuth token refresh failed for account ${account.id} (${account.providerId}).`; logger.error(message); await setTokenRefreshError(account.id, message, db); throw new Error(message); @@ -135,7 +140,7 @@ export const ensureFreshAccountToken = async ( }, }); - logger.debug(`Successfully refreshed OAuth token for account ${account.id} (${account.provider}).`); + logger.debug(`Successfully refreshed OAuth token for account ${account.id} (${account.providerId}).`); return refreshResponse.access_token; }; @@ -147,72 +152,60 @@ const setTokenRefreshError = async (accountId: string, message: string, db: Pris }; const refreshOAuthToken = async ( - provider: SupportedProvider, + providerId: string, + providerType: SupportedProviderType, refreshToken: string, ): Promise => { try { const config = await loadConfig(env.CONFIG_PATH); - const identityProviders = config?.identityProviders ?? []; - const providerConfigs = identityProviders.filter(idp => idp.provider === provider); + const idpConfig = config.identityProviders ? + config.identityProviders[providerId] : + undefined; // If no provider configs in the config file, try deprecated env vars. - if (providerConfigs.length === 0) { - const envCredentials = getDeprecatedEnvCredentials(provider); + if (!idpConfig) { + const envCredentials = getDeprecatedEnvCredentials(providerType); if (envCredentials) { - logger.debug(`Using deprecated env vars for ${provider} token refresh`); - const result = await tryRefreshToken(provider, refreshToken, envCredentials); + logger.debug(`Using deprecated env vars for ${providerType} token refresh`); + const result = await tryRefreshToken(providerType, refreshToken, envCredentials); if (result) { return result; } - logger.error(`Failed to refresh ${provider} token using deprecated env credentials`); + logger.error(`Failed to refresh ${providerType} token using deprecated env credentials`); return null; } - logger.error(`No provider config or env credentials found for: ${provider}`); + logger.error(`No provider config or env credentials found for: ${providerType}`); 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 { - const linkedAccountProviderConfig = providerConfig as - GitHubIdentityProviderConfig | - GitLabIdentityProviderConfig | - BitbucketCloudIdentityProviderConfig | - BitbucketServerIdentityProviderConfig; - - // Get client credentials from config - const clientId = await getTokenFromConfig(linkedAccountProviderConfig.clientId); - const clientSecret = await getTokenFromConfig(linkedAccountProviderConfig.clientSecret); - const baseUrl = 'baseUrl' in linkedAccountProviderConfig - ? linkedAccountProviderConfig.baseUrl - : undefined; - - const result = await tryRefreshToken(provider, refreshToken, { clientId, clientSecret, baseUrl }); - if (result) { - return result; - } - } catch (configError) { - logger.debug(`Error trying provider config for ${provider}:`, configError); - continue; - } + const linkedAccountProviderConfig = idpConfig as + GitHubIdentityProviderConfig | + GitLabIdentityProviderConfig | + BitbucketCloudIdentityProviderConfig | + BitbucketServerIdentityProviderConfig; + + // Get client credentials from config + const clientId = await getTokenFromConfig(linkedAccountProviderConfig.clientId); + const clientSecret = await getTokenFromConfig(linkedAccountProviderConfig.clientSecret); + const baseUrl = 'baseUrl' in linkedAccountProviderConfig + ? linkedAccountProviderConfig.baseUrl + : undefined; + + const result = await tryRefreshToken(providerType, refreshToken, { clientId, clientSecret, baseUrl }); + if (result) { + return result; } - logger.error(`All provider configs failed for: ${provider}`); + logger.error(`Token refresh failed for ${providerId}`); return null; } catch (e) { - logger.error(`Error refreshing ${provider} token:`, e); + logger.error(`Error refreshing ${providerType} token:`, e); return null; } }; const tryRefreshToken = async ( - provider: SupportedProvider, + providerType: SupportedProviderType, refreshToken: string, credentials: ProviderCredentials, ): Promise => { @@ -223,27 +216,27 @@ const tryRefreshToken = async ( // Use a trailing-slash-normalized base so relative paths append correctly, // preserving any context path (e.g. https://example.com/bitbucket/). const base = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'; - if (provider === 'github') { + if (providerType === 'github') { url = new URL('login/oauth/access_token', base).toString(); - } else if (provider === 'bitbucket-server') { + } else if (providerType === 'bitbucket-server') { url = new URL('rest/oauth2/latest/token', base).toString(); } else { url = new URL('oauth/token', base).toString(); } - } else if (provider === 'github') { + } else if (providerType === 'github') { url = 'https://github.com/login/oauth/access_token'; - } else if (provider === 'gitlab') { + } else if (providerType === 'gitlab') { url = 'https://gitlab.com/oauth/token'; - } else if (provider === 'bitbucket-cloud') { + } else if (providerType === 'bitbucket-cloud') { url = 'https://bitbucket.org/site/oauth2/access_token'; } else { - logger.error(`Unsupported provider for token refresh: ${provider}`); + logger.error(`Unsupported provider for token refresh: ${providerType}`); return null; } // Bitbucket requires client credentials via HTTP Basic Auth rather than request body params. // @see: https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/ - const useBasicAuth = provider === 'bitbucket-cloud'; + const useBasicAuth = providerType === 'bitbucket-cloud'; // Build request body parameters const bodyParams: Record = { @@ -260,7 +253,7 @@ const tryRefreshToken = async ( // GitLab requires redirect_uri to match the original authorization request // even when refreshing tokens. Use URL constructor to handle trailing slashes. - if (provider === 'gitlab') { + if (providerType === 'gitlab') { bodyParams.redirect_uri = new URL('/api/auth/callback/gitlab', env.AUTH_URL).toString(); } @@ -278,7 +271,7 @@ const tryRefreshToken = async ( if (!response.ok) { const errorText = await response.text(); - logger.error(`Failed to refresh ${provider} token: ${response.status} ${errorText}`); + logger.error(`Failed to refresh ${providerType} token: ${response.status} ${errorText}`); return null; } @@ -286,7 +279,7 @@ const tryRefreshToken = async ( const result = OAuthTokenResponseSchema.safeParse(json); if (!result.success) { - logger.error(`Invalid OAuth token response from ${provider}:\n${result.error.message}`); + logger.error(`Invalid OAuth token response from ${providerType}:\n${result.error.message}`); return null; } @@ -297,15 +290,15 @@ const tryRefreshToken = async ( * Get credentials from deprecated environment variables. * This is for backwards compatibility with deployments using env vars instead of config file. */ -const getDeprecatedEnvCredentials = (provider: string): ProviderCredentials | null => { - if (provider === 'github' && env.AUTH_EE_GITHUB_CLIENT_ID && env.AUTH_EE_GITHUB_CLIENT_SECRET) { +const getDeprecatedEnvCredentials = (providerType: string): ProviderCredentials | null => { + if (providerType === 'github' && env.AUTH_EE_GITHUB_CLIENT_ID && env.AUTH_EE_GITHUB_CLIENT_SECRET) { return { clientId: env.AUTH_EE_GITHUB_CLIENT_ID, clientSecret: env.AUTH_EE_GITHUB_CLIENT_SECRET, baseUrl: env.AUTH_EE_GITHUB_BASE_URL, }; } - if (provider === 'gitlab' && env.AUTH_EE_GITLAB_CLIENT_ID && env.AUTH_EE_GITLAB_CLIENT_SECRET) { + if (providerType === 'gitlab' && env.AUTH_EE_GITLAB_CLIENT_ID && env.AUTH_EE_GITLAB_CLIENT_SECRET) { return { clientId: env.AUTH_EE_GITLAB_CLIENT_ID, clientSecret: env.AUTH_EE_GITLAB_CLIENT_SECRET, diff --git a/packages/db/prisma/migrations/20260504230838_add_provider_id_and_type_to_account/migration.sql b/packages/db/prisma/migrations/20260504230838_add_provider_id_and_type_to_account/migration.sql new file mode 100644 index 000000000..ab202b55e --- /dev/null +++ b/packages/db/prisma/migrations/20260504230838_add_provider_id_and_type_to_account/migration.sql @@ -0,0 +1,15 @@ +-- Rename `provider` to `providerId` in place; preserves every existing value. +ALTER TABLE "Account" RENAME COLUMN "provider" TO "providerId"; + +-- Rename the unique index to match the renamed column (no constraint gap). +ALTER INDEX "Account_provider_providerAccountId_key" + RENAME TO "Account_providerId_providerAccountId_key"; + +-- Add providerType nullable, backfill from providerId, then enforce NOT NULL. +-- Pre-multi-instance, providerId (formerly `provider`) held the provider type +-- (e.g., 'github', 'gitlab'), so copying it preserves correct type semantics +-- for every existing row. New rows written via the auth adapter will set +-- providerType explicitly from a config lookup. +ALTER TABLE "Account" ADD COLUMN "providerType" TEXT; +UPDATE "Account" SET "providerType" = "providerId"; +ALTER TABLE "Account" ALTER COLUMN "providerType" SET NOT NULL; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 6f25a519d..50f522ce2 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -457,9 +457,9 @@ model Account { id String @id @default(cuid()) userId String type String - /// @note: this field generally matches the `provider` field of the `IdentityProviderConfig` schema - provider String + providerId String providerAccountId String + providerType String refresh_token String? access_token String? expires_at Int? @@ -492,7 +492,7 @@ model Account { user User @relation(fields: [userId], references: [id], onDelete: Cascade) - @@unique([provider, providerAccountId]) + @@unique([providerId, providerAccountId]) } // @see : https://authjs.dev/concepts/database-models#verificationtoken diff --git a/packages/schemas/src/v3/identityProvider.schema.ts b/packages/schemas/src/v3/identityProvider.schema.ts index 2fd27a886..1c17b57f9 100644 --- a/packages/schemas/src/v3/identityProvider.schema.ts +++ b/packages/schemas/src/v3/identityProvider.schema.ts @@ -10,6 +10,10 @@ const schema = { "provider": { "const": "github" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." + }, "purpose": { "enum": [ "sso", @@ -106,6 +110,10 @@ const schema = { "provider": { "const": "gitlab" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." + }, "purpose": { "enum": [ "sso", @@ -202,6 +210,10 @@ const schema = { "provider": { "const": "google" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google'." + }, "purpose": { "const": "sso" }, @@ -280,6 +292,10 @@ const schema = { "provider": { "const": "okta" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Okta'." + }, "purpose": { "const": "sso" }, @@ -389,6 +405,10 @@ const schema = { "provider": { "const": "keycloak" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Keycloak'." + }, "purpose": { "const": "sso" }, @@ -498,6 +518,10 @@ const schema = { "provider": { "const": "microsoft-entra-id" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'." + }, "purpose": { "const": "sso" }, @@ -607,6 +631,10 @@ const schema = { "provider": { "const": "gcp-iap" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'." + }, "purpose": { "const": "sso" }, @@ -654,6 +682,10 @@ const schema = { "provider": { "const": "bitbucket-cloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, "purpose": { "enum": [ "sso", @@ -739,6 +771,10 @@ const schema = { "provider": { "const": "authentik" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, "purpose": { "const": "sso" }, @@ -848,6 +884,10 @@ const schema = { "provider": { "const": "jumpcloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, "purpose": { "const": "sso" }, @@ -957,6 +997,10 @@ const schema = { "provider": { "const": "bitbucket-server" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, "purpose": { "enum": [ "sso", @@ -1053,6 +1097,10 @@ const schema = { "provider": { "const": "github" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." + }, "purpose": { "enum": [ "sso", @@ -1149,6 +1197,10 @@ const schema = { "provider": { "const": "gitlab" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." + }, "purpose": { "enum": [ "sso", @@ -1245,6 +1297,10 @@ const schema = { "provider": { "const": "google" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google'." + }, "purpose": { "const": "sso" }, @@ -1323,6 +1379,10 @@ const schema = { "provider": { "const": "okta" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Okta'." + }, "purpose": { "const": "sso" }, @@ -1432,6 +1492,10 @@ const schema = { "provider": { "const": "keycloak" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Keycloak'." + }, "purpose": { "const": "sso" }, @@ -1541,6 +1605,10 @@ const schema = { "provider": { "const": "microsoft-entra-id" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'." + }, "purpose": { "const": "sso" }, @@ -1650,6 +1718,10 @@ const schema = { "provider": { "const": "gcp-iap" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'." + }, "purpose": { "const": "sso" }, @@ -1697,6 +1769,10 @@ const schema = { "provider": { "const": "authentik" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, "purpose": { "const": "sso" }, @@ -1806,6 +1882,10 @@ const schema = { "provider": { "const": "bitbucket-cloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, "purpose": { "enum": [ "sso", @@ -1891,6 +1971,10 @@ const schema = { "provider": { "const": "jumpcloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, "purpose": { "const": "sso" }, @@ -2000,6 +2084,10 @@ const schema = { "provider": { "const": "bitbucket-server" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, "purpose": { "enum": [ "sso", diff --git a/packages/schemas/src/v3/identityProvider.type.ts b/packages/schemas/src/v3/identityProvider.type.ts index 5eebe2596..980aaa7b8 100644 --- a/packages/schemas/src/v3/identityProvider.type.ts +++ b/packages/schemas/src/v3/identityProvider.type.ts @@ -15,6 +15,10 @@ export type IdentityProviderConfig = export interface GitHubIdentityProviderConfig { provider: "github"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'GitHub'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { @@ -50,6 +54,10 @@ export interface GitHubIdentityProviderConfig { } export interface GitLabIdentityProviderConfig { provider: "gitlab"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'GitLab'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { @@ -85,6 +93,10 @@ export interface GitLabIdentityProviderConfig { } export interface GoogleIdentityProviderConfig { provider: "google"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Google'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -115,6 +127,10 @@ export interface GoogleIdentityProviderConfig { } export interface OktaIdentityProviderConfig { provider: "okta"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Okta'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -158,6 +174,10 @@ export interface OktaIdentityProviderConfig { } export interface KeycloakIdentityProviderConfig { provider: "keycloak"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Keycloak'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -201,6 +221,10 @@ export interface KeycloakIdentityProviderConfig { } export interface MicrosoftEntraIDIdentityProviderConfig { provider: "microsoft-entra-id"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -244,6 +268,10 @@ export interface MicrosoftEntraIDIdentityProviderConfig { } export interface GCPIAPIdentityProviderConfig { provider: "gcp-iap"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'. + */ + displayName?: string; purpose: "sso"; audience: | { @@ -261,6 +289,10 @@ export interface GCPIAPIdentityProviderConfig { } export interface AuthentikIdentityProviderConfig { provider: "authentik"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Authentik'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -304,6 +336,10 @@ export interface AuthentikIdentityProviderConfig { } export interface BitbucketCloudIdentityProviderConfig { provider: "bitbucket-cloud"; + /** + * Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { @@ -335,6 +371,10 @@ export interface BitbucketCloudIdentityProviderConfig { } export interface JumpCloudIdentityProviderConfig { provider: "jumpcloud"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -378,6 +418,10 @@ export interface JumpCloudIdentityProviderConfig { } export interface BitbucketServerIdentityProviderConfig { provider: "bitbucket-server"; + /** + * Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index 8bc054ade..40b44f25c 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -4955,2098 +4955,4377 @@ 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." - } + "oneOf": [ + { + "type": "object", + "description": "Map of identity providers keyed by a unique id.`.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" }, - "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" - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "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" - } + "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 + } + ] }, - "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." - } + "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": [ - "env" - ], - "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 + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "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." - } + "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 + } + ] }, - "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" - } + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the 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]*$" }, - "required": [ - "env" - ], - "additionalProperties": false + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "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 + "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "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." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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" + ] + }, + "BitbucketCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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 + } + ] }, - "required": [ - "env" - ], - "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 + } + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + "AuthentikIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 + } + ] }, - "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." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "JumpCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + "BitbucketServerIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "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." - } + "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 + } + ] }, - "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" - } + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "audience" - ] - }, - "BitbucketCloudIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "bitbucket-cloud" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" - ] - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" }, - "required": [ - "env" - ], - "additionalProperties": false + "accountLinkingRequired": { + "type": "boolean", + "default": 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", + "baseUrl" + ] + } }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "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" - } + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "accountLinkingRequired": { - "type": "boolean", - "default": false - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret" - ] - }, - "AuthentikIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "authentik" - }, - "purpose": { - "const": "sso" - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the 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]*$" }, - "required": [ - "env" - ], - "additionalProperties": false + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." }, - "required": [ - "env" - ], - "additionalProperties": false + "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 + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "issuer" - ] - }, - "JumpCloudIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "jumpcloud" - }, - "purpose": { - "const": "sso" - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "issuer" - ] - }, - "BitbucketServerIdentityProviderConfig": { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "bitbucket-server" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" - ] - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." }, - "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" - } + "purpose": { + "enum": [ + "sso", + "account_linking" + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "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 + } + ] }, - "required": [ - "env" - ], - "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 + } + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": 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": [ + "provider", + "purpose", + "clientId", + "clientSecret" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 + } + ] }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "baseUrl": { - "type": "string", - "description": "The URL of the Bitbucket Server/Data Center host.", - "examples": [ - "https://bitbucket.example.com" - ], - "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" - }, - "accountLinkingRequired": { - "type": "boolean", - "default": false - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "baseUrl" - ] - } - }, - "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." - } + "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": [ - "env" - ], - "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 + } + ] + } }, - { - "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": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." - } + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, + "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 + } + ] }, - "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" - } + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" + ], + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" }, - "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" - ] + "accountLinkingRequired": { + "type": "boolean", + "default": false + } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "baseUrl" + ] + } + ] + } }, - { - "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 + "additionalProperties": false + }, + { + "type": "array", + "deprecated": true, + "description": "Deprecated. Use the object form keyed by id instead.", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IdentityProviderConfig", + "definitions": { + "GitHubIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" }, - { - "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." + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "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." + "GitLabIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "GoogleIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] - } - }, - "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." + "OktaIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "KeycloakIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "MicrosoftEntraIDIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] - } - }, - "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 + "GCPIAPIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" }, - { - "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" + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "audience" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "BitbucketCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "AuthentikIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 } - }, - "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" + "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": [ - "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" ] - } - }, - "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." + "JumpCloudIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 } - }, - "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" + "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": [ - "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" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + "BitbucketServerIdentityProviderConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" + ] + }, + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" ], - "additionalProperties": false + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "baseUrl" ] - }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + } + }, + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "github" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] - } - }, - "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." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gitlab" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] - } - }, - "required": [ - "provider", - "purpose", - "audience" - ] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "authentik" - }, - "purpose": { - "const": "sso" }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "google" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "okta" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "issuer": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "keycloak" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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": "bitbucket-cloud" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "microsoft-entra-id" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to '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 } - }, - "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" + "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": [ - "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" ] }, - "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", + "additionalProperties": false, + "properties": { + "provider": { + "const": "gcp-iap" }, - { - "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" + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud 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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "audience" ] }, - "accountLinkingRequired": { - "type": "boolean", - "default": false - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret" - ] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "jumpcloud" - }, - "purpose": { - "const": "sso" - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "authentik" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, + "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 } - }, - "required": [ - "env" - ], - "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 + } + ] }, - { - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-cloud" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret" ] }, - "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", + "additionalProperties": false, + "properties": { + "provider": { + "const": "jumpcloud" }, - { - "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" + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, + "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 } - }, - "required": [ - "googleCloudSecret" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "issuer" - ] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "provider": { - "const": "bitbucket-server" - }, - "purpose": { - "enum": [ - "sso", - "account_linking" - ] - }, - "clientId": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + ] + }, + "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": [ - "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" + "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": [ - "googleCloudSecret" - ], - "additionalProperties": false + ] } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "issuer" ] }, - "clientSecret": { - "anyOf": [ - { - "type": "object", - "properties": { - "env": { - "type": "string", - "description": "The name of the environment variable that contains the token." + { + "type": "object", + "additionalProperties": false, + "properties": { + "provider": { + "const": "bitbucket-server" + }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, + "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 } - }, - "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" + "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": [ - "googleCloudSecret" + ] + }, + "baseUrl": { + "type": "string", + "description": "The URL of the Bitbucket Server/Data Center host.", + "examples": [ + "https://bitbucket.example.com" ], - "additionalProperties": false + "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" + }, + "accountLinkingRequired": { + "type": "boolean", + "default": false } + }, + "required": [ + "provider", + "purpose", + "clientId", + "clientSecret", + "baseUrl" ] - }, - "baseUrl": { - "type": "string", - "description": "The URL of the Bitbucket Server/Data Center host.", - "examples": [ - "https://bitbucket.example.com" - ], - "pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$" - }, - "accountLinkingRequired": { - "type": "boolean", - "default": false } - }, - "required": [ - "provider", - "purpose", - "clientId", - "clientSecret", - "baseUrl" ] } - ] - } + } + ] } }, "additionalProperties": false diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index 6a5f2e6de..7fa7f5a17 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -26,6 +26,10 @@ export type LanguageModel = | OpenRouterLanguageModel | XaiLanguageModel; export type AppConfig = GitHubAppConfig; +/** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` "^[a-zA-Z0-9_-]+$". + */ export type IdentityProviderConfig = | GitHubIdentityProviderConfig | GitLabIdentityProviderConfig @@ -66,7 +70,11 @@ export interface SourcebotConfig { /** * Defines a collection of identity providers that are available to Sourcebot. */ - identityProviders?: IdentityProviderConfig[]; + identityProviders?: + | { + [k: string]: IdentityProviderConfig; + } + | IdentityProviderConfig[]; } /** * Defines the global settings for Sourcebot. @@ -1343,6 +1351,10 @@ export interface GitHubAppConfig { } export interface GitHubIdentityProviderConfig { provider: "github"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'GitHub'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { @@ -1378,6 +1390,10 @@ export interface GitHubIdentityProviderConfig { } export interface GitLabIdentityProviderConfig { provider: "gitlab"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'GitLab'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { @@ -1413,6 +1429,10 @@ export interface GitLabIdentityProviderConfig { } export interface GoogleIdentityProviderConfig { provider: "google"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Google'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -1443,6 +1463,10 @@ export interface GoogleIdentityProviderConfig { } export interface OktaIdentityProviderConfig { provider: "okta"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Okta'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -1486,6 +1510,10 @@ export interface OktaIdentityProviderConfig { } export interface KeycloakIdentityProviderConfig { provider: "keycloak"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Keycloak'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -1529,6 +1557,10 @@ export interface KeycloakIdentityProviderConfig { } export interface MicrosoftEntraIDIdentityProviderConfig { provider: "microsoft-entra-id"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -1572,6 +1604,10 @@ export interface MicrosoftEntraIDIdentityProviderConfig { } export interface GCPIAPIdentityProviderConfig { provider: "gcp-iap"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'. + */ + displayName?: string; purpose: "sso"; audience: | { @@ -1589,6 +1625,10 @@ export interface GCPIAPIdentityProviderConfig { } export interface AuthentikIdentityProviderConfig { provider: "authentik"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'Authentik'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -1632,6 +1672,10 @@ export interface AuthentikIdentityProviderConfig { } export interface BitbucketCloudIdentityProviderConfig { provider: "bitbucket-cloud"; + /** + * Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { @@ -1663,6 +1707,10 @@ export interface BitbucketCloudIdentityProviderConfig { } export interface JumpCloudIdentityProviderConfig { provider: "jumpcloud"; + /** + * Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'. + */ + displayName?: string; purpose: "sso"; clientId: | { @@ -1706,6 +1754,10 @@ export interface JumpCloudIdentityProviderConfig { } export interface BitbucketServerIdentityProviderConfig { provider: "bitbucket-server"; + /** + * Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'. + */ + displayName?: string; purpose: "sso" | "account_linking"; clientId: | { diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index 38a30bf59..180b38b68 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -43,9 +43,12 @@ export const PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES: CodeHostType[] = [ 'bitbucketServer', ]; -export const PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS: IdentityProviderType[] = [ +export const PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS = [ 'github', 'gitlab', 'bitbucket-cloud', 'bitbucket-server', -]; \ No newline at end of file +] as const satisfies IdentityProviderType[]; + +export const doesIdpSupportPermissionSyncing = (providerType: string): providerType is (typeof PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS)[number] => + PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS.includes(providerType as (typeof PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS)[number]); \ No newline at end of file diff --git a/packages/shared/src/env.server.ts b/packages/shared/src/env.server.ts index 4f51b5b29..56bb3ad4a 100644 --- a/packages/shared/src/env.server.ts +++ b/packages/shared/src/env.server.ts @@ -1,5 +1,5 @@ import { indexSchema } from "@sourcebot/schemas/v3/index.schema"; -import { SourcebotConfig } from "@sourcebot/schemas/v3/index.type"; +import { IdentityProviderConfig, SourcebotConfig } from "@sourcebot/schemas/v3/index.type"; import { createEnv } from "@t3-oss/env-core"; import { Ajv } from "ajv"; import { readFile } from 'fs/promises'; @@ -7,6 +7,11 @@ import stripJsonComments from "strip-json-comments"; import { z } from "zod"; import { getTokenFromConfig } from "./crypto.js"; +export type NormalizedIdentityProviders = Record; +export type NormalizedSourcebotConfig = Omit & { + identityProviders?: NormalizedIdentityProviders; +}; + // Booleans are specified as 'true' or 'false' strings. const booleanSchema = z.enum(["true", "false"]); @@ -19,7 +24,7 @@ const ajv = new Ajv({ validateFormats: false, }); -export const resolveEnvironmentVariableOverridesFromConfig = async (config: SourcebotConfig): Promise> => { +export const resolveEnvironmentVariableOverridesFromConfig = async (config: NormalizedSourcebotConfig): Promise> => { if (!config.environmentOverrides) { return {}; } @@ -55,7 +60,39 @@ export const isRemotePath = (path: string) => { return path.startsWith('https://') || path.startsWith('http://'); } -export const loadConfig = async (configPath?: string): Promise => { +/** + * Collapses the dual-form `identityProviders` field into the canonical object + * form keyed by id. The array form is deprecated and only supports a single + * instance per provider type - its synthesized id is `entry.provider`, which + * matches the value historically stored in `Account.provider` for those users, + * so existing single-instance deployments don't need a data migration. + */ +const normalizeIdentityProviders = ( + raw: SourcebotConfig["identityProviders"], +): NormalizedIdentityProviders | undefined => { + if (!raw) { + return undefined; + } + if (!Array.isArray(raw)) { + return raw; + } + + const result: NormalizedIdentityProviders = {}; + for (const entry of raw) { + const id = entry.provider; + if (result[id]) { + throw new Error( + `Duplicate identity provider id "${id}" in array-form \`identityProviders\`. ` + + `The array form is deprecated and only supports one instance per provider type. ` + + `Migrate to the object form (keyed by id) to configure multiple instances.`, + ); + } + result[id] = entry; + } + return result; +}; + +export const loadConfig = async (configPath?: string): Promise => { if (!configPath) { throw new Error('CONFIG_PATH is required but not provided'); } @@ -108,7 +145,11 @@ export const loadConfig = async (configPath?: string): Promise if (!isValidConfig) { throw new Error(`Config file '${configPath}' is invalid: ${ajv.errorsText(ajv.errors)}`); } - return config; + + return { + ...config, + identityProviders: normalizeIdentityProviders(config.identityProviders), + }; } // Merge process.env with environment variables resolved from config.json diff --git a/packages/shared/src/index.server.ts b/packages/shared/src/index.server.ts index 8be1b35c2..a67f4b0af 100644 --- a/packages/shared/src/index.server.ts +++ b/packages/shared/src/index.server.ts @@ -37,6 +37,9 @@ export { loadConfig, isRemotePath, } from "./env.server.js"; +export type { + NormalizedSourcebotConfig, +} from "./env.server.js"; export { env } from "./env.server.js" export { createLogger, diff --git a/packages/web/src/app/(app)/settings/linked-accounts/page.tsx b/packages/web/src/app/(app)/settings/linked-accounts/page.tsx index d0411174e..50fbf260b 100644 --- a/packages/web/src/app/(app)/settings/linked-accounts/page.tsx +++ b/packages/web/src/app/(app)/settings/linked-accounts/page.tsx @@ -48,7 +48,7 @@ export default async function LinkedAccountsPage() { .sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)) .map((account) => ( ))} diff --git a/packages/web/src/app/api/(server)/ee/permissionSyncStatus/api.ts b/packages/web/src/app/api/(server)/ee/permissionSyncStatus/api.ts index f9b32cdee..14238f6c0 100644 --- a/packages/web/src/app/api/(server)/ee/permissionSyncStatus/api.ts +++ b/packages/web/src/app/api/(server)/ee/permissionSyncStatus/api.ts @@ -2,8 +2,8 @@ import { ServiceError } from "@/lib/serviceError"; import { withAuth } from "@/middleware/withAuth"; -import { env } from "@sourcebot/shared"; import { getEntitlements } from "@/lib/entitlements"; +import { env, PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS } from "@sourcebot/shared"; import { AccountPermissionSyncJobStatus } from "@sourcebot/db"; import { StatusCodes } from "http-status-codes"; import { ErrorCode } from "@/lib/errorCodes"; @@ -32,7 +32,7 @@ export const getPermissionSyncStatus = async (): Promise p.purpose === "sso" && - !["credentials", "nodemailer"].includes(p.id) + !["credentials", "nodemailer"].includes(p.type) ); - const hasCredentials = providers.some(p => p.purpose === "sso" && p.id === "credentials"); - const hasMagicLink = providers.some(p => p.purpose === "sso" && p.id === "nodemailer"); + const hasCredentials = providers.some(p => p.purpose === "sso" && p.type === "credentials"); + const hasMagicLink = providers.some(p => p.purpose === "sso" && p.type === "nodemailer"); if (oauthProviders.length === 0 && !hasCredentials && !hasMagicLink) { return ( @@ -64,11 +64,11 @@ export const AuthMethodSelector = ({ ...(oauthProviders.length > 0 ? [
{oauthProviders.map((provider) => { - const providerInfo = getAuthProviderInfo(provider.id); + const providerInfo = getAuthProviderInfo(provider.type); return ( { onSignInWithOauth(provider.id); diff --git a/packages/web/src/app/login/components/loginForm.tsx b/packages/web/src/app/login/components/loginForm.tsx index dac25ba9b..15f120245 100644 --- a/packages/web/src/app/login/components/loginForm.tsx +++ b/packages/web/src/app/login/components/loginForm.tsx @@ -43,9 +43,9 @@ export const LoginForm = ({ callbackUrl, error, providers, context, isAnonymousA } }, [error]); - // Helper function to get the correct analytics event name - const getLoginEventName = (providerId: string) => { - switch (providerId) { + // Helper function to get the correct analytics event name based on provider type. + const getLoginEventName = (providerType: string) => { + switch (providerType) { case "github": return "wa_login_with_github" as const; case "google": @@ -67,7 +67,8 @@ export const LoginForm = ({ callbackUrl, error, providers, context, isAnonymousA // Analytics callback for provider clicks const handleProviderClick = (providerId: string) => { - captureEvent(getLoginEventName(providerId), {}); + const providerType = providers.find(p => p.id === providerId)?.type ?? providerId; + captureEvent(getLoginEventName(providerType), {}); }; return ( diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts index 5da6cd06a..ccf733528 100644 --- a/packages/web/src/auth.ts +++ b/packages/web/src/auth.ts @@ -25,10 +25,24 @@ import { captureEvent } from '@/lib/posthog'; export const runtime = 'nodejs'; export type IdentityProvider = { - provider: Provider; + /** Provider type (e.g., 'github', 'gitlab') — used to pick icon / display defaults. */ + type: string; + /** Provider instance id (e.g., 'github', 'gitlab-corp') — used for `signIn(provider)`. */ + id: string; + /** Optional admin-supplied display name from config; overrides type-derived defaults in the UI. */ + displayName?: string; purpose: "sso" | "account_linking"; issuerUrl?: string; required?: boolean; + /** + * @warning don't use this field directly - this is meant to be + * passed directly to auth.js. Use the fields directly on the + * IdentityProvider type (i.e., `type`, `id`, etc.) + * + * @deprecated this field isn't actually deprected, but adding + * this tag to dissuade usage. + */ + __provider: Provider; } export type SessionUser = { @@ -61,7 +75,7 @@ export const getProviders = async () => { const smtpConnectionUrl = getSMTPConnectionURL(); if (smtpConnectionUrl && env.EMAIL_FROM_ADDRESS && env.AUTH_EMAIL_CODE_LOGIN_ENABLED === 'true') { providers.push({ - provider: EmailProvider({ + __provider: EmailProvider({ server: smtpConnectionUrl, from: env.EMAIL_FROM_ADDRESS, maxAge: 60 * 10, @@ -84,14 +98,17 @@ export const getProviders = async () => { if (failed.length) { throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`); } - } - }), purpose: "sso" + }, + }), + type: "nodemailer", + id: "nodemailer", + purpose: "sso", }); } if (env.AUTH_CREDENTIALS_LOGIN_ENABLED === 'true') { providers.push({ - provider: Credentials({ + __provider: Credentials({ credentials: { email: {}, password: {} @@ -146,7 +163,10 @@ export const getProviders = async () => { }; } } - }), purpose: "sso" + }), + type: "credentials", + id: "credentials", + purpose: "sso" }); } @@ -175,12 +195,12 @@ const nextAuthResult = NextAuth({ account.provider !== 'credentials' && account.providerAccountId ) { - const issuerUrl = await getIssuerUrlForAccount(account); + const issuerUrl = await getIssuerUrlForProviderId(account.provider); await __unsafePrisma.account.update({ where: { - provider_providerAccountId: { - provider: account.provider, + providerId_providerAccountId: { + providerId: account.provider, providerAccountId: account.providerAccountId, }, }, @@ -297,7 +317,7 @@ const nextAuthResult = NextAuth({ }); for (const account of accountsWithoutIssuerUrl) { - const issuerUrl = await getIssuerUrlForAccount(account); + const issuerUrl = await getIssuerUrlForProviderId(account.providerId); if (issuerUrl) { await __unsafePrisma.account.update({ where: { @@ -326,7 +346,7 @@ const nextAuthResult = NextAuth({ return session; }, }, - providers: (await getProviders()).map((provider) => provider.provider), + providers: (await 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 @@ -369,17 +389,11 @@ export const auth = cache(async (): Promise => { }); /** - * Returns the issuer URL for a given auth.js account + * Returns the issuer URL for a given identity provider id (i.e., the auth.js + * provider id, which is also what we store in `Account.providerId`). */ -const getIssuerUrlForAccount = async (account: { provider: string; }) => { +const getIssuerUrlForProviderId = async (providerId: string) => { const providers = await getProviders(); - const matchingProvider = providers.find((provider) => { - if (typeof provider.provider === "function") { - const providerInfo = provider.provider(); - return providerInfo.id === account.provider; - } else { - return provider.provider.id === account.provider; - } - }); + const matchingProvider = providers.find((provider) => provider.id === providerId); return matchingProvider?.issuerUrl; } diff --git a/packages/web/src/ee/features/sso/actions.ts b/packages/web/src/ee/features/sso/actions.ts index 4c770b4cf..ab72a4489 100644 --- a/packages/web/src/ee/features/sso/actions.ts +++ b/packages/web/src/ee/features/sso/actions.ts @@ -5,14 +5,19 @@ import { OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants"; import { withAuth } from "@/middleware/withAuth"; import { withMinimumOrgRole } from "@/middleware/withMinimumOrgRole"; import { OrgRole } from "@sourcebot/db"; -import { createLogger, env, IdentityProviderType, loadConfig, PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS } from "@sourcebot/shared"; import { hasEntitlement } from "@/lib/entitlements"; +import { createLogger, doesIdpSupportPermissionSyncing, env, loadConfig } from "@sourcebot/shared"; import { cookies } from "next/headers"; const logger = createLogger('web-ee-sso-actions'); export type LinkedAccount = { - provider: string; + /** Provider instance id (e.g., 'github', 'gitlab-corp') — used for `signIn(provider)`. */ + providerId: string; + /** Provider type (e.g., 'github', 'gitlab') — used to pick icon / display defaults. */ + providerType: string; + /** Optional admin-supplied display name from config; overrides type-derived defaults in the UI. */ + displayName?: string; isLinked: boolean; // Present when isLinked = true accountId?: string; @@ -32,48 +37,54 @@ export const getLinkedAccounts = async () => sew(() => where: { userId: user.id }, select: { id: true, - provider: true, + providerType: true, + providerId: true, providerAccountId: true, tokenRefreshErrorMessage: true, }, }); const config = await loadConfig(env.CONFIG_PATH); - const identityProviderConfigs = config.identityProviders ?? []; const permissionSyncEnabled = env.PERMISSION_SYNC_ENABLED === 'true' && await hasEntitlement('permission-syncing'); - const accountsByProvider = new Map(accounts.map(a => [a.provider, a])); const result: LinkedAccount[] = []; // All connected accounts (from DB), enriched with config data where available for (const account of accounts) { - const providerConfig = identityProviderConfigs.find(c => c.provider === account.provider); + const providerConfig = config.identityProviders ? + config.identityProviders[account.providerId] : + undefined; const isAccountLinking = providerConfig?.purpose === 'account_linking'; result.push({ - provider: account.provider, + providerId: account.providerId, + providerType: account.providerType, + displayName: providerConfig?.displayName, isLinked: true, accountId: account.id, providerAccountId: account.providerAccountId, error: account.tokenRefreshErrorMessage ?? undefined, isAccountLinkingProvider: isAccountLinking, required: isAccountLinking ? (providerConfig?.accountLinkingRequired ?? false) : false, - supportsPermissionSync: permissionSyncEnabled && PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS.includes(account.provider as IdentityProviderType), + supportsPermissionSync: permissionSyncEnabled && doesIdpSupportPermissionSyncing(account.providerType), }); } // Unlinked account_linking providers from config (not yet connected) - for (const providerConfig of identityProviderConfigs) { - if (!accountsByProvider.has(providerConfig.provider)) { + for (const [id, providerConfig] of Object.entries(config.identityProviders ?? {})) { + const account = accounts.find((account) => account.providerId === id); + if (!account) { result.push({ - provider: providerConfig.provider, + providerId: id, + providerType: providerConfig.provider, + displayName: providerConfig.displayName, isLinked: false, isAccountLinkingProvider: providerConfig.purpose === 'account_linking', required: providerConfig.purpose === 'account_linking' ? (providerConfig.accountLinkingRequired ?? false) : false, - supportsPermissionSync: permissionSyncEnabled && PERMISSION_SYNC_SUPPORTED_IDENTITY_PROVIDERS.includes(providerConfig.provider), + supportsPermissionSync: permissionSyncEnabled && doesIdpSupportPermissionSyncing(providerConfig.provider), }); } } @@ -84,17 +95,17 @@ export const getLinkedAccounts = async () => sew(() => ); -export const unlinkLinkedAccountProvider = async (provider: string) => sew(() => +export const unlinkLinkedAccountProvider = async (providerId: string) => sew(() => withAuth(async ({ prisma, role, user }) => withMinimumOrgRole(role, OrgRole.MEMBER, async () => { const result = await prisma.account.deleteMany({ where: { - provider, + providerId, userId: user.id, }, }); - logger.info(`Unlinked account provider ${provider} for user ${user.id}. Deleted ${result.count} account(s).`); + logger.info(`Unlinked account provider ${providerId} for user ${user.id}. Deleted ${result.count} account(s).`); return { success: true, count: result.count }; }) diff --git a/packages/web/src/ee/features/sso/components/connectAccountsCard.tsx b/packages/web/src/ee/features/sso/components/connectAccountsCard.tsx index f8302408c..ac3786c87 100644 --- a/packages/web/src/ee/features/sso/components/connectAccountsCard.tsx +++ b/packages/web/src/ee/features/sso/components/connectAccountsCard.tsx @@ -48,7 +48,7 @@ export const ConnectAccountsCard = ({ linkedAccounts, callbackUrl }: ConnectAcco .sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0)) .map(account => ( diff --git a/packages/web/src/ee/features/sso/components/linkedAccountProviderCard.tsx b/packages/web/src/ee/features/sso/components/linkedAccountProviderCard.tsx index 960b51bcf..544b88fa8 100644 --- a/packages/web/src/ee/features/sso/components/linkedAccountProviderCard.tsx +++ b/packages/web/src/ee/features/sso/components/linkedAccountProviderCard.tsx @@ -38,7 +38,8 @@ export function LinkedAccountProviderCard({ const router = useRouter(); const { toast } = useToast(); - const providerInfo = getAuthProviderInfo(linkedAccount.provider); + const providerInfo = getAuthProviderInfo(linkedAccount.providerType); + const displayName = linkedAccount.displayName ?? providerInfo.displayName; const { data: syncStatusData } = useQuery({ queryKey: ["accountSyncStatus", syncJobId], @@ -52,27 +53,27 @@ export function LinkedAccountProviderCard({ useEffect(() => { if (syncJobId && syncStatusData !== undefined && !syncStatusData.isSyncing) { setSyncJobId(null); - toast({ description: `✅ Permissions refreshed for ${providerInfo.displayName}.` }); + toast({ description: `✅ Permissions refreshed for ${displayName}.` }); router.refresh(); } - }, [syncJobId, syncStatusData, providerInfo.displayName, toast, router]); + }, [syncJobId, syncStatusData, displayName, toast, router]); const handleConnect = () => { - signIn(linkedAccount.provider, { redirectTo: callbackUrl ?? window.location.href }); + signIn(linkedAccount.providerId, { redirectTo: callbackUrl ?? window.location.href }); }; const handleDisconnect = async () => { setIsDisconnecting(true); try { - const result = await unlinkLinkedAccountProvider(linkedAccount.provider); + const result = await unlinkLinkedAccountProvider(linkedAccount.providerId); if (isServiceError(result)) { toast({ - description: `❌ Failed to disconnect ${providerInfo.displayName}. ${result.message}`, + description: `❌ Failed to disconnect ${displayName}. ${result.message}`, variant: "destructive", }); return; } - toast({ description: `✅ ${providerInfo.displayName} disconnected.` }); + toast({ description: `✅ ${displayName} disconnected.` }); router.refresh(); } catch (error) { toast({ @@ -112,12 +113,12 @@ export function LinkedAccountProviderCard({
- {providerInfo.displayName} + {displayName} {linkedAccount.required && ( Required )} diff --git a/packages/web/src/ee/features/sso/sso.ts b/packages/web/src/ee/features/sso/sso.ts index f0c72b626..ba7b20f64 100644 --- a/packages/web/src/ee/features/sso/sso.ts +++ b/packages/web/src/ee/features/sso/sso.ts @@ -2,9 +2,8 @@ import type { IdentityProvider } from "@/auth"; import { onCreateUser } from "@/lib/authUtils"; import { __unsafePrisma } from "@/prisma"; import { AuthentikIdentityProviderConfig, BitbucketCloudIdentityProviderConfig, BitbucketServerIdentityProviderConfig, GCPIAPIdentityProviderConfig, GitHubIdentityProviderConfig, GitLabIdentityProviderConfig, GoogleIdentityProviderConfig, JumpCloudIdentityProviderConfig, KeycloakIdentityProviderConfig, MicrosoftEntraIDIdentityProviderConfig, OktaIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type"; -import type { IdentityProviderType } from "@sourcebot/shared"; -import { createLogger, env, getTokenFromConfig, loadConfig } from "@sourcebot/shared"; import { hasEntitlement } from "@/lib/entitlements"; +import { createLogger, env, getTokenFromConfig, loadConfig } from "@sourcebot/shared"; import { OAuth2Client } from "google-auth-library"; import type { User as AuthJsUser } from "next-auth"; import type { Provider } from "next-auth/providers"; @@ -25,16 +24,19 @@ export const getEEIdentityProviders = async (): Promise => { const providers: IdentityProvider[] = []; const config = await loadConfig(env.CONFIG_PATH); - const identityProviders = config?.identityProviders ?? []; + const identityProviders = config?.identityProviders ?? {}; - for (const identityProvider of identityProviders) { + for (const [id, identityProvider] of Object.entries(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 ?? 'https://github.com').replace(/\/+$/, ''); providers.push({ - provider: await createGitHubProvider(clientId, clientSecret, baseUrl), + __provider: await createGitHubProvider(id, clientId, clientSecret, baseUrl), + type: identityProvider.provider, + id, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, required: providerConfig.accountLinkingRequired ?? false, issuerUrl: baseUrl, @@ -47,7 +49,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const baseUrl = (providerConfig.baseUrl ?? 'https://gitlab.com').replace(/\/+$/, ''); providers.push({ - provider: await createGitLabProvider(clientId, clientSecret, baseUrl), + __provider: await createGitLabProvider(id, clientId, clientSecret, baseUrl), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, required: providerConfig.accountLinkingRequired ?? false, issuerUrl: baseUrl, @@ -59,7 +64,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientId = await getTokenFromConfig(providerConfig.clientId); const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); providers.push({ - provider: createGoogleProvider(clientId, clientSecret), + __provider: createGoogleProvider(id, clientId, clientSecret), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, issuerUrl: 'https://accounts.google.com' }); @@ -71,7 +79,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const issuer = (await getTokenFromConfig(providerConfig.issuer)).replace(/\/+$/, ''); providers.push({ - provider: createOktaProvider(clientId, clientSecret, issuer), + __provider: createOktaProvider(id, clientId, clientSecret, issuer), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, issuerUrl: issuer }); @@ -83,7 +94,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const issuer = (await getTokenFromConfig(providerConfig.issuer)).replace(/\/+$/, ''); providers.push({ - provider: createKeycloakProvider(clientId, clientSecret, issuer), + __provider: createKeycloakProvider(id, clientId, clientSecret, issuer), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, issuerUrl: issuer }); @@ -95,7 +109,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const issuer = (await getTokenFromConfig(providerConfig.issuer)).replace(/\/+$/, ''); providers.push({ - provider: createMicrosoftEntraIDProvider(clientId, clientSecret, issuer), + __provider: createMicrosoftEntraIDProvider(id, clientId, clientSecret, issuer), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, issuerUrl: issuer }); @@ -105,7 +122,9 @@ export const getEEIdentityProviders = async (): Promise => { const providerConfig = identityProvider as GCPIAPIdentityProviderConfig; const audience = await getTokenFromConfig(providerConfig.audience); providers.push({ - provider: createGCPIAPProvider(audience), + __provider: createGCPIAPProvider(id, audience), + id, + type: identityProvider.provider, purpose: providerConfig.purpose }); } @@ -115,7 +134,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientId = await getTokenFromConfig(providerConfig.clientId); const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); providers.push({ - provider: await createBitbucketCloudProvider(clientId, clientSecret), + __provider: await createBitbucketCloudProvider(id, clientId, clientSecret), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, required: providerConfig.accountLinkingRequired ?? false, issuerUrl: 'https://bitbucket.org' @@ -128,7 +150,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const issuer = (await getTokenFromConfig(providerConfig.issuer)).replace(/\/+$/, ''); providers.push({ - provider: createAuthentikProvider(clientId, clientSecret, issuer), + __provider: createAuthentikProvider(id, clientId, clientSecret, issuer), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, issuerUrl: issuer }); @@ -140,7 +165,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const issuer = (await getTokenFromConfig(providerConfig.issuer)).replace(/\/+$/, ''); providers.push({ - provider: createJumpCloudProvider(clientId, clientSecret, issuer), + __provider: createJumpCloudProvider(id, clientId, clientSecret, issuer), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, issuerUrl: issuer }); @@ -152,7 +180,10 @@ export const getEEIdentityProviders = async (): Promise => { const clientSecret = await getTokenFromConfig(providerConfig.clientSecret); const baseUrl = providerConfig.baseUrl.replace(/\/+$/, ''); providers.push({ - provider: await createBitbucketServerProvider(clientId, clientSecret, baseUrl), + __provider: await createBitbucketServerProvider(id, clientId, clientSecret, baseUrl), + id, + type: identityProvider.provider, + displayName: providerConfig.displayName, purpose: providerConfig.purpose, required: providerConfig.accountLinkingRequired ?? false, issuerUrl: baseUrl @@ -164,15 +195,18 @@ export const getEEIdentityProviders = async (): Promise => { // 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 (Object.keys(identityProviders).length === 0) { if (env.AUTH_EE_GITHUB_CLIENT_ID && env.AUTH_EE_GITHUB_CLIENT_SECRET) { const baseUrl = (env.AUTH_EE_GITHUB_BASE_URL ?? 'https://github.com').replace(/\/+$/, ''); providers.push({ - provider: await createGitHubProvider( + __provider: await createGitHubProvider( + 'github', env.AUTH_EE_GITHUB_CLIENT_ID, env.AUTH_EE_GITHUB_CLIENT_SECRET, baseUrl ), + type: "github", + id: "github", purpose: "sso", issuerUrl: baseUrl }); @@ -181,11 +215,14 @@ export const getEEIdentityProviders = async (): Promise => { if (env.AUTH_EE_GITLAB_CLIENT_ID && env.AUTH_EE_GITLAB_CLIENT_SECRET) { const baseUrl = (env.AUTH_EE_GITLAB_BASE_URL ?? 'https://gitlab.com').replace(/\/+$/, ''); providers.push({ - provider: await createGitLabProvider( + __provider: await createGitLabProvider( + 'gitlab', env.AUTH_EE_GITLAB_CLIENT_ID, env.AUTH_EE_GITLAB_CLIENT_SECRET, baseUrl, ), + type: "gitlab", + id: "gitlab", purpose: "sso", issuerUrl: baseUrl }); @@ -193,7 +230,9 @@ export const getEEIdentityProviders = async (): Promise => { 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), + __provider: createGoogleProvider('google', env.AUTH_EE_GOOGLE_CLIENT_ID, env.AUTH_EE_GOOGLE_CLIENT_SECRET), + type: "google", + id: "google", purpose: "sso", issuerUrl: 'https://accounts.google.com' }); @@ -202,7 +241,9 @@ export const getEEIdentityProviders = async (): Promise => { if (env.AUTH_EE_OKTA_CLIENT_ID && env.AUTH_EE_OKTA_CLIENT_SECRET && env.AUTH_EE_OKTA_ISSUER) { const issuer = env.AUTH_EE_OKTA_ISSUER.replace(/\/+$/, ''); providers.push({ - provider: createOktaProvider(env.AUTH_EE_OKTA_CLIENT_ID, env.AUTH_EE_OKTA_CLIENT_SECRET, issuer), + __provider: createOktaProvider('okta', env.AUTH_EE_OKTA_CLIENT_ID, env.AUTH_EE_OKTA_CLIENT_SECRET, issuer), + type: "okta", + id: "okta", purpose: "sso", issuerUrl: issuer }); @@ -211,7 +252,9 @@ export const getEEIdentityProviders = async (): Promise => { if (env.AUTH_EE_KEYCLOAK_CLIENT_ID && env.AUTH_EE_KEYCLOAK_CLIENT_SECRET && env.AUTH_EE_KEYCLOAK_ISSUER) { const issuer = env.AUTH_EE_KEYCLOAK_ISSUER.replace(/\/+$/, ''); providers.push({ - provider: createKeycloakProvider(env.AUTH_EE_KEYCLOAK_CLIENT_ID, env.AUTH_EE_KEYCLOAK_CLIENT_SECRET, issuer), + __provider: createKeycloakProvider('keycloak', env.AUTH_EE_KEYCLOAK_CLIENT_ID, env.AUTH_EE_KEYCLOAK_CLIENT_SECRET, issuer), + type: "keycloak", + id: "keycloak", purpose: "sso", issuerUrl: issuer }); @@ -220,7 +263,9 @@ export const getEEIdentityProviders = async (): Promise => { if (env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID && env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET && env.AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER) { const issuer = env.AUTH_EE_MICROSOFT_ENTRA_ID_ISSUER.replace(/\/+$/, ''); providers.push({ - provider: createMicrosoftEntraIDProvider(env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID, env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET, issuer), + __provider: createMicrosoftEntraIDProvider('microsoft-entra-id', env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_ID, env.AUTH_EE_MICROSOFT_ENTRA_ID_CLIENT_SECRET, issuer), + type: "microsoft-entra-id", + id: "microsoft-entra-id", purpose: "sso", issuerUrl: issuer }); @@ -228,7 +273,9 @@ export const getEEIdentityProviders = async (): Promise => { if (env.AUTH_EE_GCP_IAP_ENABLED && env.AUTH_EE_GCP_IAP_AUDIENCE) { providers.push({ - provider: createGCPIAPProvider(env.AUTH_EE_GCP_IAP_AUDIENCE), + __provider: createGCPIAPProvider('gcp-iap', env.AUTH_EE_GCP_IAP_AUDIENCE), + type: "gcp-iap", + id: "gcp-iap", purpose: "sso" }); } @@ -237,9 +284,9 @@ export const getEEIdentityProviders = async (): Promise => { return providers; } -const createGitHubProvider = async (clientId: string, clientSecret: string, baseUrl: string) => { +const createGitHubProvider = async (id: string, clientId: string, clientSecret: string, baseUrl: string) => { return GitHub({ - id: 'github' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, // if this is set the provider expects GHE so we need this check @@ -265,9 +312,9 @@ const createGitHubProvider = async (clientId: string, clientSecret: string, base }); } -const createGitLabProvider = async (clientId: string, clientSecret: string, baseUrl: string) => { +const createGitLabProvider = async (id: string, clientId: string, clientSecret: string, baseUrl: string) => { return Gitlab({ - id: 'gitlab' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, authorization: { @@ -295,18 +342,18 @@ const createGitLabProvider = async (clientId: string, clientSecret: string, base }); } -const createGoogleProvider = (clientId: string, clientSecret: string) => { +const createGoogleProvider = (id: string, clientId: string, clientSecret: string) => { return Google({ - id: 'google' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, allowDangerousEmailAccountLinking: env.AUTH_EE_ALLOW_EMAIL_ACCOUNT_LINKING === 'true', }); } -const createOktaProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { +const createOktaProvider = (id: string, clientId: string, clientSecret: string, issuer: string): Provider => { return Okta({ - id: 'okta' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, issuer: issuer, @@ -314,9 +361,9 @@ const createOktaProvider = (clientId: string, clientSecret: string, issuer: stri }); } -const createKeycloakProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { +const createKeycloakProvider = (id: string, clientId: string, clientSecret: string, issuer: string): Provider => { return Keycloak({ - id: 'keycloak' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, issuer: issuer, @@ -324,9 +371,9 @@ const createKeycloakProvider = (clientId: string, clientSecret: string, issuer: }); } -const createMicrosoftEntraIDProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { +const createMicrosoftEntraIDProvider = (id: string, clientId: string, clientSecret: string, issuer: string): Provider => { return MicrosoftEntraID({ - id: 'microsoft-entra-id' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, issuer: issuer, @@ -334,9 +381,9 @@ const createMicrosoftEntraIDProvider = (clientId: string, clientSecret: string, }); } -const createBitbucketCloudProvider = async (clientId: string, clientSecret: string): Promise => { +const createBitbucketCloudProvider = async (id: string, clientId: string, clientSecret: string): Promise => { return Bitbucket({ - id: 'bitbucket-cloud' satisfies IdentityProviderType, + id, name: "Bitbucket Cloud", clientId, clientSecret, @@ -357,9 +404,9 @@ const createBitbucketCloudProvider = async (clientId: string, clientSecret: stri }); } -const createBitbucketServerProvider = async (clientId: string, clientSecret: string, baseUrl: string): Promise => { +const createBitbucketServerProvider = async (id: string, clientId: string, clientSecret: string, baseUrl: string): Promise => { return { - id: 'bitbucket-server' satisfies IdentityProviderType, + id, name: "Bitbucket Server", type: "oauth", clientId, @@ -425,9 +472,9 @@ const createBitbucketServerProvider = async (clientId: string, clientSecret: str } as Provider; } -export const createAuthentikProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { +export const createAuthentikProvider = (id: string, clientId: string, clientSecret: string, issuer: string): Provider => { return Authentik({ - id: 'authentik' satisfies IdentityProviderType, + id, clientId: clientId, clientSecret: clientSecret, issuer: issuer, @@ -435,9 +482,9 @@ export const createAuthentikProvider = (clientId: string, clientSecret: string, }); } -const createJumpCloudProvider = (clientId: string, clientSecret: string, issuer: string): Provider => { +const createJumpCloudProvider = (id: string, clientId: string, clientSecret: string, issuer: string): Provider => { return { - id: 'jumpcloud' satisfies IdentityProviderType, + id, name: "JumpCloud", type: "oidc", clientId: clientId, @@ -448,9 +495,9 @@ const createJumpCloudProvider = (clientId: string, clientSecret: string, issuer: } as Provider; } -const createGCPIAPProvider = (audience: string): Provider => { +const createGCPIAPProvider = (id: string, audience: string): Provider => { return Credentials({ - id: 'gcp-iap' satisfies IdentityProviderType, + id, name: "Google Cloud IAP", credentials: {}, authorize: async (_credentials, req) => { diff --git a/packages/web/src/lib/encryptedPrismaAdapter.ts b/packages/web/src/lib/encryptedPrismaAdapter.ts index 3a8780e07..5f9dcc328 100644 --- a/packages/web/src/lib/encryptedPrismaAdapter.ts +++ b/packages/web/src/lib/encryptedPrismaAdapter.ts @@ -1,7 +1,7 @@ import { PrismaAdapter } from "@auth/prisma-adapter"; -import type { Adapter, AdapterAccount } from "next-auth/adapters"; +import type { Adapter, AdapterAccount, AdapterUser } from "next-auth/adapters"; import { PrismaClient } from "@sourcebot/db"; -import { encryptOAuthToken } from "@sourcebot/shared"; +import { encryptOAuthToken, env, loadConfig } from "@sourcebot/shared"; /** * Encrypts OAuth tokens in account data before database storage @@ -16,15 +16,72 @@ function encryptAccountTokens(account: AdapterAccount): AdapterAccount { } /** - * Encrypted Prisma adapter that automatically encrypts OAuth tokens before storage + * Resolves the provider type for a given provider id. The id is the + * unique key in the `identityProviders` config; the type is the inner + * `provider` field of the matching config entry (github, gitlab, etc.). + * + * For deployments still on the deprecated AUTH_EE_*_CLIENT_ID env-var + * fallback, no entry exists in the normalized config map, but the + * synthesized id equals the type by construction — so the id itself is a + * valid type to fall back to. + */ +async function resolveProviderType(providerId: string): Promise { + const config = await loadConfig(env.CONFIG_PATH); + const configEntry = config.identityProviders?.[providerId]; + if (configEntry) { + return configEntry.provider; + } + return providerId; +} + +/** + * Encrypted Prisma adapter that: + * 1. Encrypts OAuth tokens in account data before persisting them. + * 2. Remaps the auth.js `provider` field onto our `providerId` / + * `providerType` columns so the same provider type (e.g., github) + * can be configured as multiple distinct instances. */ export function EncryptedPrismaAdapter(prisma: PrismaClient): Adapter { const baseAdapter = PrismaAdapter(prisma); - + return { ...baseAdapter, - linkAccount(account: AdapterAccount) { - return baseAdapter.linkAccount!(encryptAccountTokens(account)); + async linkAccount(account: AdapterAccount) { + const providerType = await resolveProviderType(account.provider); + const { provider, ...rest } = encryptAccountTokens(account); + await prisma.account.create({ + data: { + ...rest, + providerId: provider, + providerType, + }, + }); + return account; + }, + async getUserByAccount({ provider, providerAccountId }) { + const account = await prisma.account.findUnique({ + where: { + providerId_providerAccountId: { + providerId: provider, + providerAccountId, + }, + }, + include: { user: true }, + }); + // Cast: Prisma's User.email is nullable but AdapterUser.email is + // typed as `string`. The base PrismaAdapter performs the same + // implicit widening; we mirror it here. + return (account?.user ?? null) as AdapterUser | null; + }, + async unlinkAccount({ provider, providerAccountId }) { + await prisma.account.delete({ + where: { + providerId_providerAccountId: { + providerId: provider, + providerAccountId, + }, + }, + }); }, }; } diff --git a/packages/web/src/lib/identityProviders.ts b/packages/web/src/lib/identityProviders.ts index 9acbccd67..e28cb33aa 100644 --- a/packages/web/src/lib/identityProviders.ts +++ b/packages/web/src/lib/identityProviders.ts @@ -1,8 +1,12 @@ import { getProviders } from "@/auth"; export interface IdentityProviderMetadata { + /** Unique instance id (e.g., 'github', 'gitlab-corp') — used for `signIn(id)`. */ id: string; - name: string; + /** Provider type (e.g., 'github', 'gitlab') — used to pick icons / event names. */ + type: string; + /** Optional admin-supplied display name from config; overrides type-derived defaults in the UI. */ + displayName?: string; purpose: "sso" | "account_linking"; required: boolean; } @@ -10,21 +14,12 @@ export interface IdentityProviderMetadata { export const getIdentityProviderMetadata = async (): Promise => { const providers = await 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, - }; - } + return { + id: provider.id, + type: provider.type, + displayName: provider.displayName, + purpose: provider.purpose, + required: provider.required ?? false, + }; }); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/packages/web/src/lib/utils.ts b/packages/web/src/lib/utils.ts index e4373f98f..4a7533255 100644 --- a/packages/web/src/lib/utils.ts +++ b/packages/web/src/lib/utils.ts @@ -80,8 +80,8 @@ type AuthProviderInfo = { icon: { src: string; className?: string } | null; } -export const getAuthProviderInfo = (providerId: string): AuthProviderInfo => { - switch (providerId) { +export const getAuthProviderInfo = (providerType: string): AuthProviderInfo => { + switch (providerType) { case "github": return { id: "github", @@ -190,9 +190,9 @@ export const getAuthProviderInfo = (providerId: string): AuthProviderInfo => { }; default: return { - id: providerId, - name: providerId, - displayName: providerId.charAt(0).toUpperCase() + providerId.slice(1), + id: providerType, + name: providerType, + displayName: providerType.charAt(0).toUpperCase() + providerType.slice(1), icon: null, }; } diff --git a/schemas/v3/identityProvider.json b/schemas/v3/identityProvider.json index d4ac9e38f..fcc98d081 100644 --- a/schemas/v3/identityProvider.json +++ b/schemas/v3/identityProvider.json @@ -9,6 +9,10 @@ "provider": { "const": "github" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitHub'." + }, "purpose": { "enum": ["sso", "account_linking"] }, @@ -43,6 +47,10 @@ "provider": { "const": "gitlab" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'GitLab'." + }, "purpose": { "enum": ["sso", "account_linking"] }, @@ -77,6 +85,10 @@ "provider": { "const": "google" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google'." + }, "purpose": { "const": "sso" }, @@ -96,6 +108,10 @@ "provider": { "const": "okta" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Okta'." + }, "purpose": { "const": "sso" }, @@ -118,6 +134,10 @@ "provider": { "const": "keycloak" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Keycloak'." + }, "purpose": { "const": "sso" }, @@ -140,6 +160,10 @@ "provider": { "const": "microsoft-entra-id" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Microsoft Entra ID'." + }, "purpose": { "const": "sso" }, @@ -162,6 +186,10 @@ "provider": { "const": "gcp-iap" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Google Cloud IAP'." + }, "purpose": { "const": "sso" }, @@ -178,6 +206,10 @@ "provider": { "const": "bitbucket-cloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Cloud'." + }, "purpose": { "enum": ["sso", "account_linking"] }, @@ -201,6 +233,10 @@ "provider": { "const": "authentik" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'Authentik'." + }, "purpose": { "const": "sso" }, @@ -223,6 +259,10 @@ "provider": { "const": "jumpcloud" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen. Defaults to 'JumpCloud'." + }, "purpose": { "const": "sso" }, @@ -245,6 +285,10 @@ "provider": { "const": "bitbucket-server" }, + "displayName": { + "type": "string", + "description": "Optional human-readable label shown on the login screen and account settings. Defaults to 'Bitbucket Server'." + }, "purpose": { "enum": ["sso", "account_linking"] }, diff --git a/schemas/v3/index.json b/schemas/v3/index.json index d51de9618..5404d9875 100644 --- a/schemas/v3/index.json +++ b/schemas/v3/index.json @@ -152,11 +152,27 @@ } }, "identityProviders": { - "type": "array", "description": "Defines a collection of identity providers that are available to Sourcebot.", - "items": { - "$ref": "./identityProvider.json" - } + "oneOf": [ + { + "type": "object", + "description": "Map of identity providers keyed by a unique id.`.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$ref": "./identityProvider.json" + } + }, + "additionalProperties": false + }, + { + "type": "array", + "deprecated": true, + "description": "Deprecated. Use the object form keyed by id instead.", + "items": { + "$ref": "./identityProvider.json" + } + } + ] } }, "additionalProperties": false From 60d8edd175d50695866f8094b7f27f0d7b155835 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Tue, 5 May 2026 16:06:56 -0700 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 199781147..90916316b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Expired offline license keys no longer crash the process. An expired key now degrades to the unlicensed state. [#1109](https://github.com/sourcebot-dev/sourcebot/pull/1109) ### Fixed +- Fixed issue where using multiple identity providers of the same type (e.g., gitlab) would result in unexpected behaviours. [#1177](https://github.com/sourcebot-dev/sourcebot/pull/1177) - Add missing schema changes introduced in [#1170](https://github.com/sourcebot-dev/sourcebot/pull/1170). [#1176](https://github.com/sourcebot-dev/sourcebot/pull/1176) ## [4.17.1] - 2026-05-04