chore(tech-debt): Remove built-in secret manager (#592)

This commit is contained in:
Michael Sukkarieh 2025-10-31 14:33:28 -07:00 committed by GitHub
parent 581a5a0bd8
commit fd17871da4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 416 additions and 5301 deletions

View file

@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed "The account is already associated with another user" errors with GitLab oauth provider. [#584](https://github.com/sourcebot-dev/sourcebot/pull/584) - Fixed "The account is already associated with another user" errors with GitLab oauth provider. [#584](https://github.com/sourcebot-dev/sourcebot/pull/584)
- Fixed error when viewing a generic git connection in `/settings/connections`. [#588](https://github.com/sourcebot-dev/sourcebot/pull/588) - Fixed error when viewing a generic git connection in `/settings/connections`. [#588](https://github.com/sourcebot-dev/sourcebot/pull/588)
## Removed
- Removed built-in secret manager. [#592](https://github.com/sourcebot-dev/sourcebot/pull/592)
## [4.8.1] - 2025-10-29 ## [4.8.1] - 2025-10-29
### Fixed ### Fixed

View file

@ -86,7 +86,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
Azure Devops Cloud requires you to provide a PAT in order to index your repositories. To learn how to create PAT, check out the [Azure Devops docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows). Azure Devops Cloud requires you to provide a PAT in order to index your repositories. To learn how to create PAT, check out the [Azure Devops docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows).
Sourcebot needs the `Read` access for the `Code` scope in order to find and clone your repos. Sourcebot needs the `Read` access for the `Code` scope in order to find and clone your repos.
Next, provide the access token via the `token` property, either as an environment variable or a secret: Next, provide the access token via an environment variable which is referenced in the `token` property:
<Tabs> <Tabs>
<Tab title="Environment Variable"> <Tab title="Environment Variable">
@ -113,28 +113,6 @@ Next, provide the access token via the `token` property, either as an environmen
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
![](/images/secrets_list.png)
2. Add the `token` property to your connection config:
```json
{
"type": "azuredevops",
"deploymentType": "cloud",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>
## Schema reference ## Schema reference

View file

@ -100,7 +100,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
Azure Devops Server requires you to provide a PAT in order to index your repositories. To learn how to create PAT, check out the [Azure Devops docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows). Azure Devops Server requires you to provide a PAT in order to index your repositories. To learn how to create PAT, check out the [Azure Devops docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows).
Sourcebot needs the `Read` access for the `Code` scope in order to find and clone your repos. Sourcebot needs the `Read` access for the `Code` scope in order to find and clone your repos.
Next, provide the access token via the `token` property, either as an environment variable or a secret: Next, provide the access token via an environment variable which is referenced in the `token` property:
<Tabs> <Tabs>
<Tab title="Environment Variable"> <Tab title="Environment Variable">
@ -127,28 +127,6 @@ Next, provide the access token via the `token` property, either as an environmen
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
![](/images/secrets_list.png)
2. Add the `token` property to your connection config:
```json
{
"type": "azuredevops",
"deploymentType": "server",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>
## Schema reference ## Schema reference

View file

@ -81,7 +81,7 @@ In order to index private repositories, you'll need to generate a Gitea access t
![Gitea Access token creation](/images/gitea_pat_creation.png) ![Gitea Access token creation](/images/gitea_pat_creation.png)
Next, provide the access token via the `token` property, either as an environment variable or a secret: Next, provide the access token via an environment variable which is referenced in the `token` property:
<Tabs> <Tabs>
<Tab title="Environment Variable"> <Tab title="Environment Variable">
@ -107,27 +107,6 @@ Next, provide the access token via the `token` property, either as an environmen
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
![](/images/secrets_list.png)
2. Add the `token` property to your connection config:
```json
{
"type": "gitea",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>
## Connecting to a custom Gitea ## Connecting to a custom Gitea

View file

@ -128,7 +128,7 @@ In order to index private repositories, you'll need to generate a access token a
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>
Next, provide the access token via the `token` property, either as an environment variable or a secret: Next, provide the access token via an environment variable which is referenced in the `token` property:
<Tabs> <Tabs>
<Tab title="Environment Variable"> <Tab title="Environment Variable">
@ -154,27 +154,6 @@ Next, provide the access token via the `token` property, either as an environmen
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
![](/images/secrets_list.png)
2. Add the `token` property to your connection config:
```json
{
"type": "github",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>
## Connecting to a custom GitHub host ## Connecting to a custom GitHub host

View file

@ -116,7 +116,7 @@ In order to index private projects, you'll need to generate a GitLab Personal Ac
![GitLab PAT Scope](/images/gitlab_pat_scopes.png) ![GitLab PAT Scope](/images/gitlab_pat_scopes.png)
Next, provide the PAT via the `token` property, either as an environment variable or a secret: Next, provide the PAT via an environment variable which is referenced in the `token` property:
<Tabs> <Tabs>
<Tab title="Environment Variable"> <Tab title="Environment Variable">
@ -142,27 +142,6 @@ Next, provide the PAT via the `token` property, either as an environment variabl
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
![](/images/secrets_list.png)
2. Add the `token` property to your connection config:
```json
{
"type": "gitlab",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>
## Connecting to a custom GitLab host ## Connecting to a custom GitLab host

View file

@ -24,27 +24,4 @@
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your access token:
![](/images/secrets_list.png)
2. Add the `token` and `user` (username associated with the app password you created) properties to your connection config:
```json
{
"type": "bitbucket",
"deploymentType": "cloud",
"user": "myusername",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>

View file

@ -22,25 +22,4 @@
ghcr.io/sourcebot-dev/sourcebot:latest ghcr.io/sourcebot-dev/sourcebot:latest
``` ```
</Tab> </Tab>
<Tab title="Secret">
<Note>Secrets are only supported when [authentication](/docs/configuration/auth/overview) is enabled.</Note>
1. Navigate to **Secrets** in settings and create a new secret with your PAT:
![](/images/secrets_list.png)
2. Add the `token` property to your connection config:
```json
{
"type": "bitbucket",
"token": {
"secret": "mysecret"
}
// .. rest of config ..
}
```
</Tab>
</Tabs> </Tabs>

View file

@ -77,7 +77,6 @@
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -274,7 +273,6 @@
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -465,7 +463,6 @@
"token": { "token": {
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -779,7 +776,6 @@
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -976,7 +972,6 @@
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1167,7 +1162,6 @@
"token": { "token": {
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1563,7 +1557,6 @@
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1760,7 +1753,6 @@
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1951,7 +1943,6 @@
"token": { "token": {
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }

View file

@ -28,19 +28,6 @@
"privateKey": { "privateKey": {
"description": "The private key of the GitHub App.", "description": "The private key of the GitHub App.",
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -90,19 +77,6 @@
"privateKey": { "privateKey": {
"description": "The private key of the GitHub App.", "description": "The private key of the GitHub App.",
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -11,25 +11,7 @@
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -15,25 +15,7 @@
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -15,25 +15,7 @@
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -228,25 +210,7 @@
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -435,25 +399,7 @@
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -707,25 +653,7 @@
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -880,25 +808,7 @@
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -11,25 +11,7 @@
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -11,25 +11,7 @@
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -1,76 +0,0 @@
{/* THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! */}
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "GithubAppConfig",
"properties": {
"type": {
"const": "githubApp",
"description": "GitHub App Configuration"
},
"deploymentHostname": {
"type": "string",
"format": "hostname",
"default": "github.com",
"description": "The hostname of the GitHub App deployment.",
"examples": [
"github.com",
"github.example.com"
]
},
"id": {
"type": "string",
"description": "The ID of the GitHub App."
},
"privateKey": {
"description": "The private key of the GitHub App.",
"anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"env": {
"type": "string",
"description": "The name of the environment variable that contains the token. Only supported in declarative connection configs."
}
},
"required": [
"env"
],
"additionalProperties": false
}
]
}
},
"required": [
"type",
"id"
],
"oneOf": [
{
"required": [
"privateKey"
]
},
{
"required": [
"privateKeyPath"
]
}
],
"additionalProperties": false
}
```

View file

@ -11,25 +11,7 @@
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -6,19 +6,6 @@
"definitions": { "definitions": {
"Token": { "Token": {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -86,19 +73,6 @@
}, },
{ {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -130,19 +104,6 @@
}, },
{ {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -2,7 +2,6 @@ import { AzureDevOpsConnectionConfig } from "@sourcebot/schemas/v3/azuredevops.t
import { createLogger } from "@sourcebot/logger"; import { createLogger } from "@sourcebot/logger";
import { measure, fetchWithRetry } from "./utils.js"; import { measure, fetchWithRetry } from "./utils.js";
import micromatch from "micromatch"; import micromatch from "micromatch";
import { PrismaClient } from "@sourcebot/db";
import { BackendException, BackendError } from "@sourcebot/error"; import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
@ -29,13 +28,11 @@ function createAzureDevOpsConnection(
export const getAzureDevOpsReposFromConfig = async ( export const getAzureDevOpsReposFromConfig = async (
config: AzureDevOpsConnectionConfig, config: AzureDevOpsConnectionConfig,
orgId: number,
db: PrismaClient
) => { ) => {
const baseUrl = config.url || `https://${AZUREDEVOPS_CLOUD_HOSTNAME}`; const baseUrl = config.url || `https://${AZUREDEVOPS_CLOUD_HOSTNAME}`;
const token = config.token ? const token = config.token ?
await getTokenFromConfig(config.token, orgId, db) : await getTokenFromConfig(config.token) :
undefined; undefined;
if (!token) { if (!token) {

View file

@ -3,7 +3,6 @@ import { createBitbucketServerClient } from "@coderabbitai/bitbucket/server";
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type"; import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type";
import type { ClientOptions, ClientPathsWithMethod } from "openapi-fetch"; import type { ClientOptions, ClientPathsWithMethod } from "openapi-fetch";
import { createLogger } from "@sourcebot/logger"; import { createLogger } from "@sourcebot/logger";
import { PrismaClient } from "@sourcebot/db";
import { measure, fetchWithRetry } from "./utils.js"; import { measure, fetchWithRetry } from "./utils.js";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { import {
@ -58,9 +57,9 @@ type ServerPaginatedResponse<T> = {
readonly nextPageStart: number; readonly nextPageStart: number;
} }
export const getBitbucketReposFromConfig = async (config: BitbucketConnectionConfig, orgId: number, db: PrismaClient) => { export const getBitbucketReposFromConfig = async (config: BitbucketConnectionConfig) => {
const token = config.token ? const token = config.token ?
await getTokenFromConfig(config.token, orgId, db) : await getTokenFromConfig(config.token) :
undefined; undefined;
if (config.deploymentType === 'server' && !config.url) { if (config.deploymentType === 'server' && !config.url) {

View file

@ -179,25 +179,25 @@ export class ConnectionManager {
const result = await (async () => { const result = await (async () => {
switch (config.type) { switch (config.type) {
case 'github': { case 'github': {
return await compileGithubConfig(config, job.data.connectionId, orgId, this.db, abortController); return await compileGithubConfig(config, job.data.connectionId, abortController);
} }
case 'gitlab': { case 'gitlab': {
return await compileGitlabConfig(config, job.data.connectionId, orgId, this.db); return await compileGitlabConfig(config, job.data.connectionId);
} }
case 'gitea': { case 'gitea': {
return await compileGiteaConfig(config, job.data.connectionId, orgId, this.db); return await compileGiteaConfig(config, job.data.connectionId);
} }
case 'gerrit': { case 'gerrit': {
return await compileGerritConfig(config, job.data.connectionId, orgId); return await compileGerritConfig(config, job.data.connectionId);
} }
case 'bitbucket': { case 'bitbucket': {
return await compileBitbucketConfig(config, job.data.connectionId, orgId, this.db); return await compileBitbucketConfig(config, job.data.connectionId);
} }
case 'azuredevops': { case 'azuredevops': {
return await compileAzureDevOpsConfig(config, job.data.connectionId, orgId, this.db); return await compileAzureDevOpsConfig(config, job.data.connectionId);
} }
case 'git': { case 'git': {
return await compileGenericGitHostConfig(config, job.data.connectionId, orgId); return await compileGenericGitHostConfig(config, job.data.connectionId);
} }
} }
})(); })();

View file

@ -55,11 +55,7 @@ export class GithubAppManager {
for (const app of githubApps) { for (const app of githubApps) {
const deploymentHostname = app.deploymentHostname as string || GITHUB_DEFAULT_DEPLOYMENT_HOSTNAME; const deploymentHostname = app.deploymentHostname as string || GITHUB_DEFAULT_DEPLOYMENT_HOSTNAME;
const privateKey = await getTokenFromConfig(app.privateKey);
// @todo: we should move SINGLE_TENANT_ORG_ID to shared package or just remove the need to pass this in
// when resolving tokens
const SINGLE_TENANT_ORG_ID = 1;
const privateKey = await getTokenFromConfig(app.privateKey, SINGLE_TENANT_ORG_ID, this.db);
const octokitApp = new App({ const octokitApp = new App({
appId: Number(app.id), appId: Number(app.id),

View file

@ -163,7 +163,7 @@ export class RepoPermissionSyncer {
logger.info(`Syncing permissions for repo ${repo.displayName}...`); logger.info(`Syncing permissions for repo ${repo.displayName}...`);
const credentials = await getAuthCredentialsForRepo(repo, this.db, logger); const credentials = await getAuthCredentialsForRepo(repo, logger);
if (!credentials) { if (!credentials) {
throw new Error(`No credentials found for repo ${id}`); throw new Error(`No credentials found for repo ${id}`);
} }

View file

@ -4,7 +4,6 @@ import { measure } from './utils.js';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import { createLogger } from '@sourcebot/logger'; import { createLogger } from '@sourcebot/logger';
import micromatch from 'micromatch'; import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js'; import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { env } from './env.js'; import { env } from './env.js';
@ -13,13 +12,13 @@ import { getTokenFromConfig } from "@sourcebot/crypto";
const logger = createLogger('gitea'); const logger = createLogger('gitea');
const GITEA_CLOUD_HOSTNAME = "gitea.com"; const GITEA_CLOUD_HOSTNAME = "gitea.com";
export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => { export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig) => {
const hostname = config.url ? const hostname = config.url ?
new URL(config.url).hostname : new URL(config.url).hostname :
GITEA_CLOUD_HOSTNAME; GITEA_CLOUD_HOSTNAME;
const token = config.token ? const token = config.token ?
await getTokenFromConfig(config.token, orgId, db) : await getTokenFromConfig(config.token) :
hostname === GITEA_CLOUD_HOSTNAME ? hostname === GITEA_CLOUD_HOSTNAME ?
env.FALLBACK_GITEA_CLOUD_TOKEN : env.FALLBACK_GITEA_CLOUD_TOKEN :
undefined; undefined;
@ -53,7 +52,7 @@ export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, org
allRepos = allRepos.filter(repo => repo.full_name !== undefined); allRepos = allRepos.filter(repo => repo.full_name !== undefined);
allRepos = allRepos.filter(repo => { allRepos = allRepos.filter(repo => {
if (repo.full_name === undefined) { if (repo.full_name === undefined) {
logger.warn(`Repository with undefined full_name found: orgId=${orgId}, repoId=${repo.id}`); logger.warn(`Repository with undefined full_name found: repoId=${repo.id}`);
return false; return false;
} }
return true; return true;

View file

@ -1,6 +1,5 @@
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { PrismaClient } from "@sourcebot/db";
import { createLogger } from "@sourcebot/logger"; import { createLogger } from "@sourcebot/logger";
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
import { hasEntitlement } from "@sourcebot/shared"; import { hasEntitlement } from "@sourcebot/shared";
@ -92,13 +91,13 @@ const getOctokitWithGithubApp = async (
} }
} }
export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, orgId: number, db: PrismaClient, signal: AbortSignal): Promise<{ repos: OctokitRepository[], warnings: string[] }> => { export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, signal: AbortSignal): Promise<{ repos: OctokitRepository[], warnings: string[] }> => {
const hostname = config.url ? const hostname = config.url ?
new URL(config.url).hostname : new URL(config.url).hostname :
GITHUB_CLOUD_HOSTNAME; GITHUB_CLOUD_HOSTNAME;
const token = config.token ? const token = config.token ?
await getTokenFromConfig(config.token, orgId, db) : await getTokenFromConfig(config.token) :
hostname === GITHUB_CLOUD_HOSTNAME ? hostname === GITHUB_CLOUD_HOSTNAME ?
env.FALLBACK_GITHUB_CLOUD_TOKEN : env.FALLBACK_GITHUB_CLOUD_TOKEN :
undefined; undefined;

View file

@ -3,7 +3,6 @@ import micromatch from "micromatch";
import { createLogger } from "@sourcebot/logger"; import { createLogger } from "@sourcebot/logger";
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type" import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"
import { measure, fetchWithRetry } from "./utils.js"; import { measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { env } from "./env.js"; import { env } from "./env.js";
@ -34,13 +33,13 @@ export const createGitLabFromOAuthToken = async ({ oauthToken, url }: { oauthTok
}); });
} }
export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, orgId: number, db: PrismaClient) => { export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig) => {
const hostname = config.url ? const hostname = config.url ?
new URL(config.url).hostname : new URL(config.url).hostname :
GITLAB_CLOUD_HOSTNAME; GITLAB_CLOUD_HOSTNAME;
const token = config.token ? const token = config.token ?
await getTokenFromConfig(config.token, orgId, db) : await getTokenFromConfig(config.token) :
hostname === GITLAB_CLOUD_HOSTNAME ? hostname === GITLAB_CLOUD_HOSTNAME ?
env.FALLBACK_GITLAB_CLOUD_TOKEN : env.FALLBACK_GITLAB_CLOUD_TOKEN :
undefined; undefined;

View file

@ -7,7 +7,7 @@ import { BitbucketRepository, getBitbucketReposFromConfig } from "./bitbucket.js
import { getAzureDevOpsReposFromConfig } from "./azuredevops.js"; import { getAzureDevOpsReposFromConfig } from "./azuredevops.js";
import { SchemaRestRepository as BitbucketServerRepository } from "@coderabbitai/bitbucket/server/openapi"; import { SchemaRestRepository as BitbucketServerRepository } from "@coderabbitai/bitbucket/server/openapi";
import { SchemaRepository as BitbucketCloudRepository } from "@coderabbitai/bitbucket/cloud/openapi"; import { SchemaRepository as BitbucketCloudRepository } from "@coderabbitai/bitbucket/cloud/openapi";
import { CodeHostType, Prisma, PrismaClient } from '@sourcebot/db'; import { CodeHostType, Prisma } from '@sourcebot/db';
import { WithRequired } from "./types.js" import { WithRequired } from "./types.js"
import { marshalBool } from "./utils.js"; import { marshalBool } from "./utils.js";
import { createLogger } from '@sourcebot/logger'; import { createLogger } from '@sourcebot/logger';
@ -19,6 +19,7 @@ import { getOriginUrl, isPathAValidGitRepoRoot, isUrlAValidGitRepo } from './git
import assert from 'assert'; import assert from 'assert';
import GitUrlParse from 'git-url-parse'; import GitUrlParse from 'git-url-parse';
import { RepoMetadata } from '@sourcebot/shared'; import { RepoMetadata } from '@sourcebot/shared';
import { SINGLE_TENANT_ORG_ID } from './constants.js';
export type RepoData = WithRequired<Prisma.RepoCreateInput, 'connections'>; export type RepoData = WithRequired<Prisma.RepoCreateInput, 'connections'>;
@ -32,10 +33,8 @@ type CompileResult = {
export const compileGithubConfig = async ( export const compileGithubConfig = async (
config: GithubConnectionConfig, config: GithubConnectionConfig,
connectionId: number, connectionId: number,
orgId: number,
db: PrismaClient,
abortController: AbortController): Promise<CompileResult> => { abortController: AbortController): Promise<CompileResult> => {
const gitHubReposResult = await getGitHubReposFromConfig(config, orgId, db, abortController.signal); const gitHubReposResult = await getGitHubReposFromConfig(config, abortController.signal);
const gitHubRepos = gitHubReposResult.repos; const gitHubRepos = gitHubReposResult.repos;
const warnings = gitHubReposResult.warnings; const warnings = gitHubReposResult.warnings;
@ -66,7 +65,7 @@ export const compileGithubConfig = async (
isPublic: isPublic, isPublic: isPublic,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -104,11 +103,9 @@ export const compileGithubConfig = async (
export const compileGitlabConfig = async ( export const compileGitlabConfig = async (
config: GitlabConnectionConfig, config: GitlabConnectionConfig,
connectionId: number, connectionId: number): Promise<CompileResult> => {
orgId: number,
db: PrismaClient): Promise<CompileResult> => {
const gitlabReposResult = await getGitLabReposFromConfig(config, orgId, db); const gitlabReposResult = await getGitLabReposFromConfig(config);
const gitlabRepos = gitlabReposResult.repos; const gitlabRepos = gitlabReposResult.repos;
const warnings = gitlabReposResult.warnings; const warnings = gitlabReposResult.warnings;
@ -144,7 +141,7 @@ export const compileGitlabConfig = async (
isArchived: !!project.archived, isArchived: !!project.archived,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -180,11 +177,9 @@ export const compileGitlabConfig = async (
export const compileGiteaConfig = async ( export const compileGiteaConfig = async (
config: GiteaConnectionConfig, config: GiteaConnectionConfig,
connectionId: number, connectionId: number): Promise<CompileResult> => {
orgId: number,
db: PrismaClient): Promise<CompileResult> => {
const giteaReposResult = await getGiteaReposFromConfig(config, orgId, db); const giteaReposResult = await getGiteaReposFromConfig(config);
const giteaRepos = giteaReposResult.repos; const giteaRepos = giteaReposResult.repos;
const warnings = giteaReposResult.warnings; const warnings = giteaReposResult.warnings;
@ -217,7 +212,7 @@ export const compileGiteaConfig = async (
isArchived: !!repo.archived, isArchived: !!repo.archived,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -251,8 +246,7 @@ export const compileGiteaConfig = async (
export const compileGerritConfig = async ( export const compileGerritConfig = async (
config: GerritConnectionConfig, config: GerritConnectionConfig,
connectionId: number, connectionId: number): Promise<CompileResult> => {
orgId: number): Promise<CompileResult> => {
const gerritRepos = await getGerritReposFromConfig(config); const gerritRepos = await getGerritReposFromConfig(config);
const hostUrl = config.url; const hostUrl = config.url;
@ -298,7 +292,7 @@ export const compileGerritConfig = async (
isArchived: false, isArchived: false,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -332,11 +326,9 @@ export const compileGerritConfig = async (
export const compileBitbucketConfig = async ( export const compileBitbucketConfig = async (
config: BitbucketConnectionConfig, config: BitbucketConnectionConfig,
connectionId: number, connectionId: number): Promise<CompileResult> => {
orgId: number,
db: PrismaClient): Promise<CompileResult> => {
const bitbucketReposResult = await getBitbucketReposFromConfig(config, orgId, db); const bitbucketReposResult = await getBitbucketReposFromConfig(config);
const bitbucketRepos = bitbucketReposResult.repos; const bitbucketRepos = bitbucketReposResult.repos;
const warnings = bitbucketReposResult.warnings; const warnings = bitbucketReposResult.warnings;
@ -415,7 +407,7 @@ export const compileBitbucketConfig = async (
isArchived: isArchived, isArchived: isArchived,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -450,15 +442,14 @@ export const compileBitbucketConfig = async (
export const compileGenericGitHostConfig = async ( export const compileGenericGitHostConfig = async (
config: GenericGitHostConnectionConfig, config: GenericGitHostConnectionConfig,
connectionId: number, connectionId: number
orgId: number,
): Promise<CompileResult> => { ): Promise<CompileResult> => {
const configUrl = new URL(config.url); const configUrl = new URL(config.url);
if (configUrl.protocol === 'file:') { if (configUrl.protocol === 'file:') {
return compileGenericGitHostConfig_file(config, orgId, connectionId); return compileGenericGitHostConfig_file(config, connectionId);
} }
else if (configUrl.protocol === 'http:' || configUrl.protocol === 'https:') { else if (configUrl.protocol === 'http:' || configUrl.protocol === 'https:') {
return compileGenericGitHostConfig_url(config, orgId, connectionId); return compileGenericGitHostConfig_url(config, connectionId);
} }
else { else {
// Schema should prevent this, but throw an error just in case. // Schema should prevent this, but throw an error just in case.
@ -468,7 +459,6 @@ export const compileGenericGitHostConfig = async (
export const compileGenericGitHostConfig_file = async ( export const compileGenericGitHostConfig_file = async (
config: GenericGitHostConnectionConfig, config: GenericGitHostConnectionConfig,
orgId: number,
connectionId: number, connectionId: number,
): Promise<CompileResult> => { ): Promise<CompileResult> => {
const configUrl = new URL(config.url); const configUrl = new URL(config.url);
@ -518,7 +508,7 @@ export const compileGenericGitHostConfig_file = async (
isArchived: false, isArchived: false,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -547,7 +537,6 @@ export const compileGenericGitHostConfig_file = async (
export const compileGenericGitHostConfig_url = async ( export const compileGenericGitHostConfig_url = async (
config: GenericGitHostConnectionConfig, config: GenericGitHostConnectionConfig,
orgId: number,
connectionId: number, connectionId: number,
): Promise<CompileResult> => { ): Promise<CompileResult> => {
const remoteUrl = new URL(config.url); const remoteUrl = new URL(config.url);
@ -582,7 +571,7 @@ export const compileGenericGitHostConfig_url = async (
isArchived: false, isArchived: false,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {
@ -604,11 +593,9 @@ export const compileGenericGitHostConfig_url = async (
export const compileAzureDevOpsConfig = async ( export const compileAzureDevOpsConfig = async (
config: AzureDevOpsConnectionConfig, config: AzureDevOpsConnectionConfig,
connectionId: number, connectionId: number): Promise<CompileResult> => {
orgId: number,
db: PrismaClient): Promise<CompileResult> => {
const azureDevOpsReposResult = await getAzureDevOpsReposFromConfig(config, orgId, db); const azureDevOpsReposResult = await getAzureDevOpsReposFromConfig(config);
const azureDevOpsRepos = azureDevOpsReposResult.repos; const azureDevOpsRepos = azureDevOpsReposResult.repos;
const warnings = azureDevOpsReposResult.warnings; const warnings = azureDevOpsReposResult.warnings;
@ -652,7 +639,7 @@ export const compileAzureDevOpsConfig = async (
isPublic: isPublic, isPublic: isPublic,
org: { org: {
connect: { connect: {
id: orgId, id: SINGLE_TENANT_ORG_ID,
}, },
}, },
connections: { connections: {

View file

@ -290,7 +290,7 @@ export class RepoIndexManager {
const metadata = repoMetadataSchema.parse(repo.metadata); const metadata = repoMetadataSchema.parse(repo.metadata);
const credentials = await getAuthCredentialsForRepo(repo, this.db); const credentials = await getAuthCredentialsForRepo(repo);
const cloneUrlMaybeWithToken = credentials?.cloneUrlWithToken ?? repo.cloneUrl; const cloneUrlMaybeWithToken = credentials?.cloneUrlWithToken ?? repo.cloneUrl;
const authHeader = credentials?.authHeader ?? undefined; const authHeader = credentials?.authHeader ?? undefined;

View file

@ -1,7 +1,7 @@
import { Logger } from "winston"; import { Logger } from "winston";
import { RepoAuthCredentials, RepoWithConnections } from "./types.js"; import { RepoAuthCredentials, RepoWithConnections } from "./types.js";
import path from 'path'; import path from 'path';
import { PrismaClient, Repo } from "@sourcebot/db"; import { Repo } from "@sourcebot/db";
import { getTokenFromConfig } from "@sourcebot/crypto"; import { getTokenFromConfig } from "@sourcebot/crypto";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig, BitbucketConnectionConfig, AzureDevOpsConnectionConfig } from '@sourcebot/schemas/v3/connection.type'; import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig, BitbucketConnectionConfig, AzureDevOpsConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
@ -110,7 +110,7 @@ export const fetchWithRetry = async <T>(
// fetch the token here using the connections from the repo. Multiple connections could be referencing this repo, and each // fetch the token here using the connections from the repo. Multiple connections could be referencing this repo, and each
// may have their own token. This method will just pick the first connection that has a token (if one exists) and uses that. This // may have their own token. This method will just pick the first connection that has a token (if one exists) and uses that. This
// may technically cause syncing to fail if that connection's token just so happens to not have access to the repo it's referencing. // may technically cause syncing to fail if that connection's token just so happens to not have access to the repo it's referencing.
export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: PrismaClient, logger?: Logger): Promise<RepoAuthCredentials | undefined> => { export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, logger?: Logger): Promise<RepoAuthCredentials | undefined> => {
// If we have github apps configured we assume that we must use them for github service auth // If we have github apps configured we assume that we must use them for github service auth
if (repo.external_codeHostType === 'github' && hasEntitlement('github-app') && GithubAppManager.getInstance().appsConfigured()) { if (repo.external_codeHostType === 'github' && hasEntitlement('github-app') && GithubAppManager.getInstance().appsConfigured()) {
logger?.debug(`Using GitHub App for service auth for repo ${repo.displayName} hosted at ${repo.external_codeHostUrl}`); logger?.debug(`Using GitHub App for service auth for repo ${repo.displayName} hosted at ${repo.external_codeHostUrl}`);
@ -139,7 +139,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
if (connection.connectionType === 'github') { if (connection.connectionType === 'github') {
const config = connection.config as unknown as GithubConnectionConfig; const config = connection.config as unknown as GithubConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, db); const token = await getTokenFromConfig(config.token);
return { return {
hostUrl: config.url, hostUrl: config.url,
token, token,
@ -154,7 +154,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
} else if (connection.connectionType === 'gitlab') { } else if (connection.connectionType === 'gitlab') {
const config = connection.config as unknown as GitlabConnectionConfig; const config = connection.config as unknown as GitlabConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, db); const token = await getTokenFromConfig(config.token);
return { return {
hostUrl: config.url, hostUrl: config.url,
token, token,
@ -170,7 +170,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
} else if (connection.connectionType === 'gitea') { } else if (connection.connectionType === 'gitea') {
const config = connection.config as unknown as GiteaConnectionConfig; const config = connection.config as unknown as GiteaConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, db); const token = await getTokenFromConfig(config.token);
return { return {
hostUrl: config.url, hostUrl: config.url,
token, token,
@ -185,7 +185,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
} else if (connection.connectionType === 'bitbucket') { } else if (connection.connectionType === 'bitbucket') {
const config = connection.config as unknown as BitbucketConnectionConfig; const config = connection.config as unknown as BitbucketConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, db); const token = await getTokenFromConfig(config.token);
const username = config.user ?? 'x-token-auth'; const username = config.user ?? 'x-token-auth';
return { return {
hostUrl: config.url, hostUrl: config.url,
@ -202,7 +202,7 @@ export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: P
} else if (connection.connectionType === 'azuredevops') { } else if (connection.connectionType === 'azuredevops') {
const config = connection.config as unknown as AzureDevOpsConnectionConfig; const config = connection.config as unknown as AzureDevOpsConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, db); const token = await getTokenFromConfig(config.token);
// For ADO server, multiple auth schemes may be supported. If the ADO deployment supports NTLM, the git clone will default // For ADO server, multiple auth schemes may be supported. If the ADO deployment supports NTLM, the git clone will default
// to this over basic auth. As a result, we cannot embed the token in the clone URL and must force basic auth by passing in the token // to this over basic auth. As a result, we cannot embed the token in the clone URL and must force basic auth by passing in the token

View file

@ -1,26 +1,7 @@
import { PrismaClient } from "@sourcebot/db";
import { Token } from "@sourcebot/schemas/v3/shared.type"; import { Token } from "@sourcebot/schemas/v3/shared.type";
import { decrypt } from "./index.js";
export const getTokenFromConfig = async (token: Token, orgId: number, db: PrismaClient) => { export const getTokenFromConfig = async (token: Token) => {
if ('secret' in token) { if ('env' in token) {
const secretKey = token.secret;
const secret = await db.secret.findUnique({
where: {
orgId_key: {
key: secretKey,
orgId
}
}
});
if (!secret) {
throw new Error(`Secret with key ${secretKey} not found for org ${orgId}`);
}
const decryptedToken = decrypt(secret.iv, secret.encryptedValue);
return decryptedToken;
} else if ('env' in token) {
const envToken = process.env[token.env]; const envToken = process.env[token.env];
if (!envToken) { if (!envToken) {
throw new Error(`Environment variable ${token.env} not found.`); throw new Error(`Environment variable ${token.env} not found.`);

View file

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the `Secret` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Secret" DROP CONSTRAINT "Secret_orgId_fkey";
-- DropTable
DROP TABLE "Secret";

View file

@ -253,7 +253,6 @@ model Org {
members UserToOrg[] members UserToOrg[]
connections Connection[] connections Connection[]
repos Repo[] repos Repo[]
secrets Secret[]
apiKeys ApiKey[] apiKeys ApiKey[]
isOnboarded Boolean @default(false) isOnboarded Boolean @default(false)
imageUrl String? imageUrl String?
@ -303,19 +302,6 @@ model UserToOrg {
@@id([orgId, userId]) @@id([orgId, userId])
} }
model Secret {
orgId Int
key String
encryptedValue String
iv String
createdAt DateTime @default(now())
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
@@id([orgId, key])
}
model ApiKey { model ApiKey {
name String name String
hash String @id @unique hash String @id @unique

View file

@ -76,7 +76,6 @@ const schema = {
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -273,7 +272,6 @@ const schema = {
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -464,7 +462,6 @@ const schema = {
"token": { "token": {
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -778,7 +775,6 @@ const schema = {
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -975,7 +971,6 @@ const schema = {
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1166,7 +1161,6 @@ const schema = {
"token": { "token": {
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1562,7 +1556,6 @@ const schema = {
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1759,7 +1752,6 @@ const schema = {
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }
@ -1950,7 +1942,6 @@ const schema = {
"token": { "token": {
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ {
"env": "ENV_VAR_CONTAINING_TOKEN" "env": "ENV_VAR_CONTAINING_TOKEN"
} }

View file

@ -27,19 +27,6 @@ const schema = {
"privateKey": { "privateKey": {
"description": "The private key of the GitHub App.", "description": "The private key of the GitHub App.",
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -89,19 +76,6 @@ const schema = {
"privateKey": { "privateKey": {
"description": "The private key of the GitHub App.", "description": "The private key of the GitHub App.",
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -18,14 +18,7 @@ export interface GitHubAppConfig {
/** /**
* The private key of the GitHub App. * The private key of the GitHub App.
*/ */
privateKey: privateKey: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -10,25 +10,7 @@ const schema = {
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -8,14 +8,7 @@ export interface AzureDevOpsConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token: token: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -14,25 +14,7 @@ const schema = {
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -12,14 +12,7 @@ export interface BitbucketConnectionConfig {
/** /**
* An authentication token. * An authentication token.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -14,25 +14,7 @@ const schema = {
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -227,25 +209,7 @@ const schema = {
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -434,25 +398,7 @@ const schema = {
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -706,25 +652,7 @@ const schema = {
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -879,25 +807,7 @@ const schema = {
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -17,14 +17,7 @@ export interface GithubConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -106,14 +99,7 @@ export interface GitlabConnectionConfig {
/** /**
* An authentication token. * An authentication token.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -177,14 +163,7 @@ export interface GiteaConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -263,14 +242,7 @@ export interface BitbucketConnectionConfig {
/** /**
* An authentication token. * An authentication token.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -320,14 +292,7 @@ export interface AzureDevOpsConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token: token: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -10,25 +10,7 @@ const schema = {
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -8,14 +8,7 @@ export interface GiteaConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -10,25 +10,7 @@ const schema = {
}, },
"token": { "token": {
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -8,14 +8,7 @@ export interface GithubConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -10,25 +10,7 @@ const schema = {
}, },
"token": { "token": {
"description": "An authentication token.", "description": "An authentication token.",
"examples": [
{
"secret": "SECRET_KEY"
}
],
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -8,14 +8,7 @@ export interface GitlabConnectionConfig {
/** /**
* An authentication token. * An authentication token.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

File diff suppressed because it is too large Load diff

View file

@ -155,14 +155,7 @@ export interface GithubConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -244,14 +237,7 @@ export interface GitlabConnectionConfig {
/** /**
* An authentication token. * An authentication token.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -315,14 +301,7 @@ export interface GiteaConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -401,14 +380,7 @@ export interface BitbucketConnectionConfig {
/** /**
* An authentication token. * An authentication token.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -458,14 +430,7 @@ export interface AzureDevOpsConnectionConfig {
/** /**
* A Personal Access Token (PAT). * A Personal Access Token (PAT).
*/ */
token: token: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -551,14 +516,7 @@ export interface AmazonBedrockLanguageModel {
/** /**
* Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable. * Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.
*/ */
accessKeyId?: accessKeyId?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -567,14 +525,7 @@ export interface AmazonBedrockLanguageModel {
/** /**
* Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable. * Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.
*/ */
accessKeySecret?: accessKeySecret?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -583,14 +534,7 @@ export interface AmazonBedrockLanguageModel {
/** /**
* Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable. * Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable.
*/ */
sessionToken?: sessionToken?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -616,20 +560,12 @@ export interface LanguageModelHeaders {
*/ */
[k: string]: [k: string]:
| string | string
| (
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| { | {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
env: string; env: string;
} };
);
} }
export interface AnthropicLanguageModel { export interface AnthropicLanguageModel {
/** /**
@ -647,14 +583,7 @@ export interface AnthropicLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -686,14 +615,7 @@ export interface AzureLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `AZURE_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `AZURE_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -725,14 +647,7 @@ export interface DeepSeekLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `DEEPSEEK_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `DEEPSEEK_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -760,14 +675,7 @@ export interface GoogleGenerativeAILanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -803,14 +711,7 @@ export interface GoogleVertexAnthropicLanguageModel {
/** /**
* Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.
*/ */
credentials?: credentials?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -846,14 +747,7 @@ export interface GoogleVertexLanguageModel {
/** /**
* Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.
*/ */
credentials?: credentials?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -881,14 +775,7 @@ export interface MistralLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `MISTRAL_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `MISTRAL_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -916,14 +803,7 @@ export interface OpenAILanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -955,14 +835,7 @@ export interface OpenAICompatibleLanguageModel {
/** /**
* Optional API key. If specified, adds an `Authorization` header to request headers with the value Bearer <token>. * Optional API key. If specified, adds an `Authorization` header to request headers with the value Bearer <token>.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -989,20 +862,12 @@ export interface LanguageModelQueryParams {
*/ */
[k: string]: [k: string]:
| string | string
| (
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| { | {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
env: string; env: string;
} };
);
} }
export interface OpenRouterLanguageModel { export interface OpenRouterLanguageModel {
/** /**
@ -1020,14 +885,7 @@ export interface OpenRouterLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `OPENROUTER_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `OPENROUTER_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -1055,14 +913,7 @@ export interface XaiLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `XAI_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `XAI_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -1090,14 +941,7 @@ export interface GitHubAppConfig {
/** /**
* The private key of the GitHub App. * The private key of the GitHub App.
*/ */
privateKey: privateKey: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

File diff suppressed because it is too large Load diff

View file

@ -30,14 +30,7 @@ export interface AmazonBedrockLanguageModel {
/** /**
* Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable. * Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.
*/ */
accessKeyId?: accessKeyId?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -46,14 +39,7 @@ export interface AmazonBedrockLanguageModel {
/** /**
* Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable. * Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.
*/ */
accessKeySecret?: accessKeySecret?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -62,14 +48,7 @@ export interface AmazonBedrockLanguageModel {
/** /**
* Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable. * Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable.
*/ */
sessionToken?: sessionToken?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -95,20 +74,12 @@ export interface LanguageModelHeaders {
*/ */
[k: string]: [k: string]:
| string | string
| (
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| { | {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
env: string; env: string;
} };
);
} }
export interface AnthropicLanguageModel { export interface AnthropicLanguageModel {
/** /**
@ -126,14 +97,7 @@ export interface AnthropicLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `ANTHROPIC_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -165,14 +129,7 @@ export interface AzureLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `AZURE_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `AZURE_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -204,14 +161,7 @@ export interface DeepSeekLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `DEEPSEEK_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `DEEPSEEK_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -239,14 +189,7 @@ export interface GoogleGenerativeAILanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -282,14 +225,7 @@ export interface GoogleVertexAnthropicLanguageModel {
/** /**
* Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.
*/ */
credentials?: credentials?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -325,14 +261,7 @@ export interface GoogleVertexLanguageModel {
/** /**
* Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials. * Optional file path to service account credentials JSON. Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or application default credentials.
*/ */
credentials?: credentials?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -360,14 +289,7 @@ export interface MistralLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `MISTRAL_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `MISTRAL_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -395,14 +317,7 @@ export interface OpenAILanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `OPENAI_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -434,14 +349,7 @@ export interface OpenAICompatibleLanguageModel {
/** /**
* Optional API key. If specified, adds an `Authorization` header to request headers with the value Bearer <token>. * Optional API key. If specified, adds an `Authorization` header to request headers with the value Bearer <token>.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -468,20 +376,12 @@ export interface LanguageModelQueryParams {
*/ */
[k: string]: [k: string]:
| string | string
| (
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| { | {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
env: string; env: string;
} };
);
} }
export interface OpenRouterLanguageModel { export interface OpenRouterLanguageModel {
/** /**
@ -499,14 +399,7 @@ export interface OpenRouterLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `OPENROUTER_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `OPENROUTER_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */
@ -534,14 +427,7 @@ export interface XaiLanguageModel {
/** /**
* Optional API key to use with the model. Defaults to the `XAI_API_KEY` environment variable. * Optional API key to use with the model. Defaults to the `XAI_API_KEY` environment variable.
*/ */
token?: token?: {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -5,19 +5,6 @@ const schema = {
"definitions": { "definitions": {
"Token": { "Token": {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -85,19 +72,6 @@ const schema = {
}, },
{ {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {
@ -129,19 +103,6 @@ const schema = {
}, },
{ {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -4,14 +4,7 @@
* This interface was referenced by `Shared`'s JSON-Schema * This interface was referenced by `Shared`'s JSON-Schema
* via the `definition` "Token". * via the `definition` "Token".
*/ */
export type Token = export type Token = {
| {
/**
* The name of the secret that contains the token.
*/
secret: string;
}
| {
/** /**
* The name of the environment variable that contains the token. Only supported in declarative connection configs. * The name of the environment variable that contains the token. Only supported in declarative connection configs.
*/ */

View file

@ -255,89 +255,6 @@ export const completeOnboarding = async (domain: string): Promise<{ success: boo
}) })
)); ));
export const getSecrets = async (domain: string): Promise<{ createdAt: Date; key: string; }[] | ServiceError> => sew(() =>
withAuth((userId) =>
withOrgMembership(userId, domain, async ({ org }) => {
const secrets = await prisma.secret.findMany({
where: {
orgId: org.id,
},
select: {
key: true,
createdAt: true
}
});
return secrets.map((secret) => ({
key: secret.key,
createdAt: secret.createdAt,
}));
})));
export const createSecret = async (key: string, value: string, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
withAuth((userId) =>
withOrgMembership(userId, domain, async ({ org }) => {
const encrypted = encrypt(value);
const existingSecret = await prisma.secret.findUnique({
where: {
orgId_key: {
orgId: org.id,
key,
}
}
});
if (existingSecret) {
return secretAlreadyExists();
}
await prisma.secret.create({
data: {
orgId: org.id,
key,
encryptedValue: encrypted.encryptedData,
iv: encrypted.iv,
}
});
return {
success: true,
}
})));
export const checkIfSecretExists = async (key: string, domain: string): Promise<boolean | ServiceError> => sew(() =>
withAuth((userId) =>
withOrgMembership(userId, domain, async ({ org }) => {
const secret = await prisma.secret.findUnique({
where: {
orgId_key: {
orgId: org.id,
key,
}
}
});
return !!secret;
})));
export const deleteSecret = async (key: string, domain: string): Promise<{ success: boolean } | ServiceError> => sew(() =>
withAuth((userId) =>
withOrgMembership(userId, domain, async ({ org }) => {
await prisma.secret.delete({
where: {
orgId_key: {
orgId: org.id,
key,
}
}
});
return {
success: true,
}
})));
export const verifyApiKey = async (apiKeyPayload: ApiKeyPayload): Promise<{ apiKey: ApiKey } | ServiceError> => sew(async () => { export const verifyApiKey = async (apiKeyPayload: ApiKeyPayload): Promise<{ apiKey: ApiKey } | ServiceError> => sew(async () => {
const parts = apiKeyPayload.apiKey.split("-"); const parts = apiKeyPayload.apiKey.split("-");
if (parts.length !== 2 || parts[0] !== "sourcebot") { if (parts.length !== 2 || parts[0] !== "sourcebot") {
@ -1778,21 +1695,21 @@ export const getRepoImage = async (repoId: number): Promise<ArrayBuffer | Servic
if (connection.connectionType === 'github') { if (connection.connectionType === 'github') {
const config = connection.config as unknown as GithubConnectionConfig; const config = connection.config as unknown as GithubConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, prisma); const token = await getTokenFromConfig(config.token);
authHeaders['Authorization'] = `token ${token}`; authHeaders['Authorization'] = `token ${token}`;
break; break;
} }
} else if (connection.connectionType === 'gitlab') { } else if (connection.connectionType === 'gitlab') {
const config = connection.config as unknown as GitlabConnectionConfig; const config = connection.config as unknown as GitlabConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, prisma); const token = await getTokenFromConfig(config.token);
authHeaders['PRIVATE-TOKEN'] = token; authHeaders['PRIVATE-TOKEN'] = token;
break; break;
} }
} else if (connection.connectionType === 'gitea') { } else if (connection.connectionType === 'gitea') {
const config = connection.config as unknown as GiteaConnectionConfig; const config = connection.config as unknown as GiteaConnectionConfig;
if (config.token) { if (config.token) {
const token = await getTokenFromConfig(config.token, connection.orgId, prisma); const token = await getTokenFromConfig(config.token);
authHeaders['Authorization'] = `token ${token}`; authHeaders['Authorization'] = `token ${token}`;
break; break;
} }

View file

@ -1,315 +0,0 @@
'use client';
import { checkIfSecretExists, createSecret } from "@/actions";
import { useToast } from "@/components/hooks/use-toast";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useDomain } from "@/hooks/useDomain";
import { isServiceError } from "@/lib/utils";
import githubPatCreation from "@/public/github_pat_creation.png";
import gitlabPatCreation from "@/public/gitlab_pat_creation.png";
import giteaPatCreation from "@/public/gitea_pat_creation.png";
import { CodeHostType } from "@sourcebot/db";
import { zodResolver } from "@hookform/resolvers/zod";
import { Eye, EyeOff, Loader2 } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useCallback, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
interface ImportSecretDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSecretCreated: (key: string) => void;
codeHostType: CodeHostType;
}
export const ImportSecretDialog = ({ open, onOpenChange, onSecretCreated, codeHostType }: ImportSecretDialogProps) => {
const [showValue, setShowValue] = useState(false);
const domain = useDomain();
const { toast } = useToast();
const captureEvent = useCaptureEvent();
const formSchema = z.object({
key: z.string().min(1).refine(async (key) => {
const doesSecretExist = await checkIfSecretExists(key, domain);
if(!isServiceError(doesSecretExist)) {
captureEvent('wa_secret_combobox_import_secret_fail', {
type: codeHostType,
error: "A secret with this key already exists.",
});
}
return isServiceError(doesSecretExist) || !doesSecretExist;
}, "A secret with this key already exists."),
value: z.string().min(1),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
key: "",
value: "",
},
});
const { isSubmitting } = form.formState;
const onSubmit = useCallback(async (data: z.infer<typeof formSchema>) => {
const response = await createSecret(data.key, data.value, domain);
if (isServiceError(response)) {
toast({
description: `❌ Failed to create secret. Reason: ${response.message}`
});
captureEvent('wa_secret_combobox_import_secret_fail', {
type: codeHostType,
error: response.message,
});
} else {
toast({
description: `✅ Secret created successfully!`
});
captureEvent('wa_secret_combobox_import_secret_success', {
type: codeHostType,
});
form.reset();
onOpenChange(false);
onSecretCreated(data.key);
}
}, [domain, toast, onOpenChange, onSecretCreated, form, codeHostType, captureEvent]);
const codeHostSpecificStep = useMemo(() => {
switch (codeHostType) {
case 'github':
return <GitHubPATCreationStep step={1} />;
case 'gitlab':
return <GitLabPATCreationStep step={1} />;
case 'bitbucketCloud':
return <BitbucketCloudPATCreationStep step={1} />;
case 'bitbucketServer':
return <BitbucketServerPATCreationStep step={1} />;
case 'gitea':
return <GiteaPATCreationStep step={1} />;
case 'gerrit':
return null;
}
}, [codeHostType]);
return (
<Dialog
open={open}
onOpenChange={onOpenChange}
>
<DialogContent
className="p-16 max-w-[90vw] sm:max-w-2xl max-h-[80vh] overflow-scroll rounded-lg"
>
<DialogHeader>
<DialogTitle className="text-2xl font-semibold">Import a secret</DialogTitle>
<DialogDescription>
Secrets are used to authenticate with a code host. They are encrypted at rest using <Link href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" target="_blank" className="underline">AES-256-CBC</Link>.
Checkout our <Link href="https://sourcebot.dev/security" target="_blank" className="underline">security docs</Link> for more information.
</DialogDescription>
</DialogHeader>
<Form
{...form}
>
<form
className="space-y-4 flex flex-col mt-4 gap-4"
onSubmit={(event) => {
event.stopPropagation();
form.handleSubmit(onSubmit)(event);
}}
>
{codeHostSpecificStep}
<SecretCreationStep
step={2}
title="Import the secret"
description="Copy the generated token and paste it below."
>
<FormField
control={form.control}
name="value"
render={({ field }) => (
<FormItem>
<FormLabel>Value</FormLabel>
<FormControl>
<div className="relative">
<Input
{...field}
type={showValue ? "text" : "password"}
placeholder="Enter your secret value"
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-2 top-1/2 -translate-y-1/2"
onClick={() => setShowValue(!showValue)}
>
{showValue ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div>
</FormControl>
<FormDescription>
The secret value to store securely.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</SecretCreationStep>
<SecretCreationStep
step={3}
title="Name the secret"
description="Give the secret a unique name so that it can be referenced in a connection config."
>
<FormField
control={form.control}
name="key"
render={({ field }) => (
<FormItem>
<FormLabel>Key</FormLabel>
<FormControl>
<Input
placeholder="my-access-token"
{...field}
/>
</FormControl>
<FormDescription>
A unique name to identify this secret.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</SecretCreationStep>
<div className="flex justify-end w-full">
<Button
type="submit"
disabled={isSubmitting}
>
{isSubmitting && <Loader2 className="h-4 w-4 animate-spin mr-2" />}
Import Secret
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
)
}
const GitHubPATCreationStep = ({ step }: { step: number }) => {
return (
<SecretCreationStep
step={step}
title="Create a Personal Access Token"
description=<span>Navigate <Link href="https://github.com/settings/tokens/new" target="_blank" className="underline">here on github.com</Link> (or your enterprise instance) and create a new personal access token. Sourcebot needs the <strong>repo</strong> scope in order to access private repositories:</span>
>
<Image
className="mx-auto rounded-sm"
src={githubPatCreation}
alt="Create a personal access token"
width={500}
height={500}
/>
</SecretCreationStep>
)
}
const GitLabPATCreationStep = ({ step }: { step: number }) => {
return (
<SecretCreationStep
step={step}
title="Create a Personal Access Token"
description=<span>Navigate <Link href="https://gitlab.com/-/user_settings/personal_access_tokens" target="_blank" className="underline">here on gitlab.com</Link> (or your self-hosted instance) and create a new personal access token. Sourcebot needs the <strong>read_api</strong> scope in order to access private projects:</span>
>
<Image
className="mx-auto rounded-sm"
src={gitlabPatCreation}
alt="Create a personal access token"
width={600}
height={600}
/>
</SecretCreationStep>
)
}
const GiteaPATCreationStep = ({ step }: { step: number }) => {
return (
<SecretCreationStep
step={step}
title="Create a Personal Access Token"
description=<span>Navigate <Link href="https://gitea.com/user/settings/applications" target="_blank" className="underline">here on gitea.com</Link> (or your self-hosted instance) and create a new access token. Sourcebot needs the <strong>read:repository</strong>, <strong>read:user</strong>, and <strong>read:organization</strong> scopes:</span>
>
<Image
className="mx-auto rounded-sm"
src={giteaPatCreation}
alt="Create a personal access token"
width={600}
height={600}
/>
</SecretCreationStep>
)
}
const BitbucketCloudPATCreationStep = ({ step }: { step: number }) => {
return (
<SecretCreationStep
step={step}
title="Create an Access Token"
description=<span>Please check out our <Link href="https://docs.sourcebot.dev/docs/connections/bitbucket-cloud#authenticating-with-bitbucket-cloud" target="_blank" className="underline">docs</Link> for more information on how to create auth credentials for Bitbucket Cloud.</span>
>
</SecretCreationStep>
)
}
const BitbucketServerPATCreationStep = ({ step }: { step: number }) => {
return (
<SecretCreationStep
step={step}
title="Create an Access Token"
description=<span>Please check out our <Link href="https://docs.sourcebot.dev/docs/connections/bitbucket-data-center#authenticating-with-bitbucket-data-center" target="_blank" className="underline">docs</Link> for more information on how to create auth credentials for Bitbucket Data Center.</span>
>
</SecretCreationStep>
)
}
interface SecretCreationStepProps {
step: number;
title: string;
description: string | React.ReactNode;
children?: React.ReactNode;
}
const SecretCreationStep = ({ step, title, description, children }: SecretCreationStepProps) => {
return (
<div className="relative flex flex-col gap-2">
<div className="absolute -left-10 flex flex-col items-center gap-2 h-full">
<span className="text-md font-semibold border rounded-full px-2">{step}</span>
<Separator className="h-5/6" orientation="vertical" />
</div>
<h3 className="text-md font-semibold">
{title}
</h3>
<p className="text-sm text-muted-foreground">
{description}
</p>
{children}
</div>
)
}

View file

@ -106,10 +106,6 @@ export default async function SettingsLayout(
isNotificationDotVisible: connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0, isNotificationDotVisible: connectionStats.numberOfConnectionsWithFirstTimeSyncJobsInProgress > 0,
} }
] : []), ] : []),
{
title: "Secrets",
href: `/${domain}/settings/secrets`,
},
{ {
title: "API Keys", title: "API Keys",
href: `/${domain}/settings/apiKeys`, href: `/${domain}/settings/apiKeys`,

View file

@ -1,67 +0,0 @@
'use client';
import { CodeHostIconButton } from "@/app/[domain]/components/codeHostIconButton";
import { Card, CardTitle, CardDescription, CardHeader, CardContent } from "@/components/ui/card";
import { getCodeHostIcon } from "@/lib/utils";
import { cn } from "@/lib/utils";
import { CodeHostType } from "@sourcebot/db";
import { useState } from "react";
import { ImportSecretDialog } from "@/app/[domain]/components/importSecretDialog";
import { useRouter } from "next/navigation";
interface ImportSecretCardProps {
className?: string;
}
export const ImportSecretCard = ({ className }: ImportSecretCardProps) => {
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
const [isImportSecretDialogOpen, setIsImportSecretDialogOpen] = useState(false);
const router = useRouter();
return (
<>
<Card className={cn(className)}>
<CardHeader>
<CardTitle>Import a new secret</CardTitle>
<CardDescription>Import a secret from a code host to allow Sourcebot to sync private repositories.</CardDescription>
</CardHeader>
<CardContent className="flex flex-row gap-4 w-full justify-center">
<CodeHostIconButton
name="GitHub"
logo={getCodeHostIcon("github")}
onClick={() => {
setSelectedCodeHost("github");
setIsImportSecretDialogOpen(true);
}}
/>
<CodeHostIconButton
name="GitLab"
logo={getCodeHostIcon("gitlab")}
onClick={() => {
setSelectedCodeHost("gitlab");
setIsImportSecretDialogOpen(true);
}}
/>
<CodeHostIconButton
name="Gitea"
logo={getCodeHostIcon("gitea")}
onClick={() => {
setSelectedCodeHost("gitea");
setIsImportSecretDialogOpen(true);
}}
/>
</CardContent>
</Card>
{selectedCodeHost && (
<ImportSecretDialog
open={isImportSecretDialogOpen}
onOpenChange={setIsImportSecretDialogOpen}
onSecretCreated={() => {
router.refresh();
}}
codeHostType={selectedCodeHost ?? "github"}
/>
)}
</>
)
}

View file

@ -1,158 +0,0 @@
'use client';
import { Input } from "@/components/ui/input";
import { LucideKeyRound, MoreVertical, Search, LucideTrash } from "lucide-react";
import { useState, useMemo, useCallback } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { getFormattedDate, isServiceError } from "@/lib/utils";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { deleteSecret } from "@/actions";
import { useDomain } from "@/hooks/useDomain";
import { useToast } from "@/components/hooks/use-toast";
import { useRouter } from "next/navigation";
import { CodeSnippet } from "@/app/components/codeSnippet";
interface Secret {
key: string;
createdAt: Date;
}
interface SecretsListProps {
secrets: Secret[];
}
export const SecretsList = ({ secrets }: SecretsListProps) => {
const [searchQuery, setSearchQuery] = useState("");
const [dateSort, setDateSort] = useState<"newest" | "oldest">("newest");
const [secretToDelete, setSecretToDelete] = useState<Secret | null>(null);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const domain = useDomain();
const { toast } = useToast();
const router = useRouter();
const filteredSecrets = useMemo(() => {
return secrets
.filter((secret) => {
const searchLower = searchQuery.toLowerCase();
const matchesSearch = secret.key.toLowerCase().includes(searchLower);
return matchesSearch;
})
.sort((a, b) => {
return dateSort === "newest"
? b.createdAt.getTime() - a.createdAt.getTime()
: a.createdAt.getTime() - b.createdAt.getTime()
});
}, [secrets, searchQuery, dateSort]);
const onDeleteSecret = useCallback(() => {
deleteSecret(secretToDelete?.key ?? "", domain)
.then((response) => {
if (isServiceError(response)) {
toast({
description: `❌ Failed to delete secret. Reason: ${response.message}`
})
} else {
toast({
description: `✅ Secret deleted successfully.`
});
router.refresh();
}
})
}, [domain, secretToDelete?.key, toast, router]);
return (
<div className="w-full mx-auto space-y-6">
<div className="flex gap-4 flex-col sm:flex-row">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Filter by secret name..."
className="pl-9"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<Select value={dateSort} onValueChange={(value) => setDateSort(value as "newest" | "oldest")}>
<SelectTrigger className="w-[140px]">
<SelectValue placeholder="Date" />
</SelectTrigger>
<SelectContent>
<SelectItem value="newest">Newest</SelectItem>
<SelectItem value="oldest">Oldest</SelectItem>
</SelectContent>
</Select>
</div>
<div className="border rounded-lg overflow-hidden">
<div className="max-h-[600px] overflow-y-auto divide-y">
{secrets.length === 0 || (filteredSecrets.length === 0 && searchQuery.length > 0) ? (
<div className="flex flex-col items-center justify-center h-96 p-4">
<p className="font-medium text-sm">No Secrets Found</p>
<p className="text-sm text-muted-foreground mt-2">
{filteredSecrets.length === 0 && searchQuery.length > 0 ? "No secrets found matching your filters." : "Use the form above to create a new secret."}
</p>
</div>
) : (
filteredSecrets.map((secret) => (
<div key={secret.key} className="p-4 flex items-center justify-between bg-background">
<div className="flex items-center">
<LucideKeyRound className="w-4 h-4 mr-2" />
<p className="font-mono">{secret.key}</p>
</div>
<div className="flex items-center gap-4">
<p className="text-sm text-muted-foreground">
Created {getFormattedDate(secret.createdAt)}
</p>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
className="cursor-pointer text-destructive"
onClick={() => {
setSecretToDelete(secret);
setIsDeleteDialogOpen(true);
}}
>
<LucideTrash className="w-4 h-4 mr-2" />
Delete secret
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
))
)}
</div>
</div>
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Secret</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete the secret <CodeSnippet>{secretToDelete?.key}</CodeSnippet>? Any connections that use this secret will <strong>fail to sync.</strong>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
onClick={onDeleteSecret}
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}

View file

@ -1,36 +0,0 @@
import { getSecrets } from "@/actions";
import { SecretsList } from "./components/secretsList";
import { isServiceError } from "@/lib/utils";
import { ImportSecretCard } from "./components/importSecretCard";
import { ServiceErrorException } from "@/lib/serviceError";
interface SecretsPageProps {
params: Promise<{
domain: string;
}>
}
export default async function SecretsPage(props: SecretsPageProps) {
const params = await props.params;
const {
domain
} = params;
const secrets = await getSecrets(domain);
if (isServiceError(secrets)) {
throw new ServiceErrorException(secrets);
}
return (
<div className="flex flex-col gap-6">
<div>
<h3 className="text-lg font-medium">Manage Secrets</h3>
<p className="text-sm text-muted-foreground">These secrets grant Sourcebot access to private code.</p>
</div>
<SecretsList secrets={secrets} />
<ImportSecretCard className="mt-4" />
</div>
)
}

View file

@ -92,7 +92,7 @@ export async function POST(req: Request) {
}); });
} }
const { model, providerOptions } = await _getAISDKLanguageModelAndOptions(languageModelConfig, org.id); const { model, providerOptions } = await _getAISDKLanguageModelAndOptions(languageModelConfig);
return createMessageStreamResponse({ return createMessageStreamResponse({
messages, messages,

View file

@ -21,7 +21,7 @@ import { createXai } from '@ai-sdk/xai';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { getTokenFromConfig } from "@sourcebot/crypto"; import { getTokenFromConfig } from "@sourcebot/crypto";
import { ChatVisibility, OrgRole, Prisma, PrismaClient } from "@sourcebot/db"; import { ChatVisibility, OrgRole, Prisma } from "@sourcebot/db";
import { LanguageModel } from "@sourcebot/schemas/v3/languageModel.type"; import { LanguageModel } from "@sourcebot/schemas/v3/languageModel.type";
import { Token } from "@sourcebot/schemas/v3/shared.type"; import { Token } from "@sourcebot/schemas/v3/shared.type";
import { loadConfig } from "@sourcebot/shared"; import { loadConfig } from "@sourcebot/shared";
@ -204,7 +204,7 @@ export const generateAndUpdateChatNameFromMessage = async ({ chatId, languageMod
}); });
} }
const { model } = await _getAISDKLanguageModelAndOptions(languageModelConfig, org.id); const { model } = await _getAISDKLanguageModelAndOptions(languageModelConfig);
const prompt = `Convert this question into a short topic title (max 50 characters). const prompt = `Convert this question into a short topic title (max 50 characters).
@ -374,7 +374,7 @@ export const _getConfiguredLanguageModelsFull = async (): Promise<LanguageModel[
} }
export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, orgId: number): Promise<{ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel): Promise<{
model: AISDKLanguageModelV2, model: AISDKLanguageModelV2,
providerOptions?: Record<string, Record<string, JSONValue>>, providerOptions?: Record<string, Record<string, JSONValue>>,
}> => { }> => {
@ -386,16 +386,16 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
baseURL: config.baseUrl, baseURL: config.baseUrl,
region: config.region ?? env.AWS_REGION, region: config.region ?? env.AWS_REGION,
accessKeyId: config.accessKeyId accessKeyId: config.accessKeyId
? await getTokenFromConfig(config.accessKeyId, orgId, prisma) ? await getTokenFromConfig(config.accessKeyId)
: env.AWS_ACCESS_KEY_ID, : env.AWS_ACCESS_KEY_ID,
secretAccessKey: config.accessKeySecret secretAccessKey: config.accessKeySecret
? await getTokenFromConfig(config.accessKeySecret, orgId, prisma) ? await getTokenFromConfig(config.accessKeySecret)
: env.AWS_SECRET_ACCESS_KEY, : env.AWS_SECRET_ACCESS_KEY,
sessionToken: config.sessionToken sessionToken: config.sessionToken
? await getTokenFromConfig(config.sessionToken, orgId, prisma) ? await getTokenFromConfig(config.sessionToken)
: env.AWS_SESSION_TOKEN, : env.AWS_SESSION_TOKEN,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
// Fallback to the default Node.js credential provider chain if no credentials are provided. // Fallback to the default Node.js credential provider chain if no credentials are provided.
// See: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/#fromnodeproviderchain // See: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/#fromnodeproviderchain
@ -412,10 +412,10 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const anthropic = createAnthropic({ const anthropic = createAnthropic({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: env.ANTHROPIC_API_KEY, : env.ANTHROPIC_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -434,11 +434,11 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
case 'azure': { case 'azure': {
const azure = createAzure({ const azure = createAzure({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token ? (await getTokenFromConfig(config.token, orgId, prisma)) : env.AZURE_API_KEY, apiKey: config.token ? (await getTokenFromConfig(config.token)) : env.AZURE_API_KEY,
apiVersion: config.apiVersion, apiVersion: config.apiVersion,
resourceName: config.resourceName ?? env.AZURE_RESOURCE_NAME, resourceName: config.resourceName ?? env.AZURE_RESOURCE_NAME,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -449,9 +449,9 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
case 'deepseek': { case 'deepseek': {
const deepseek = createDeepSeek({ const deepseek = createDeepSeek({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token ? (await getTokenFromConfig(config.token, orgId, prisma)) : env.DEEPSEEK_API_KEY, apiKey: config.token ? (await getTokenFromConfig(config.token)) : env.DEEPSEEK_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -463,10 +463,10 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const google = createGoogleGenerativeAI({ const google = createGoogleGenerativeAI({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: env.GOOGLE_GENERATIVE_AI_API_KEY, : env.GOOGLE_GENERATIVE_AI_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -480,11 +480,11 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
location: config.region ?? env.GOOGLE_VERTEX_REGION, location: config.region ?? env.GOOGLE_VERTEX_REGION,
...(config.credentials ? { ...(config.credentials ? {
googleAuthOptions: { googleAuthOptions: {
keyFilename: await getTokenFromConfig(config.credentials, orgId, prisma), keyFilename: await getTokenFromConfig(config.credentials),
} }
} : {}), } : {}),
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -506,11 +506,11 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
location: config.region ?? env.GOOGLE_VERTEX_REGION, location: config.region ?? env.GOOGLE_VERTEX_REGION,
...(config.credentials ? { ...(config.credentials ? {
googleAuthOptions: { googleAuthOptions: {
keyFilename: await getTokenFromConfig(config.credentials, orgId, prisma), keyFilename: await getTokenFromConfig(config.credentials),
} }
} : {}), } : {}),
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -522,10 +522,10 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const mistral = createMistral({ const mistral = createMistral({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: env.MISTRAL_API_KEY, : env.MISTRAL_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -537,10 +537,10 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const openai = createOpenAI({ const openai = createOpenAI({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: env.OPENAI_API_KEY, : env.OPENAI_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -558,13 +558,13 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
baseURL: config.baseUrl, baseURL: config.baseUrl,
name: config.displayName ?? modelId, name: config.displayName ?? modelId,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: undefined, : undefined,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
queryParams: config.queryParams queryParams: config.queryParams
? await extractLanguageModelKeyValuePairs(config.queryParams, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.queryParams)
: undefined, : undefined,
}); });
@ -585,10 +585,10 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const openrouter = createOpenRouter({ const openrouter = createOpenRouter({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: env.OPENROUTER_API_KEY, : env.OPENROUTER_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -600,10 +600,10 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const xai = createXai({ const xai = createXai({
baseURL: config.baseUrl, baseURL: config.baseUrl,
apiKey: config.token apiKey: config.token
? await getTokenFromConfig(config.token, orgId, prisma) ? await getTokenFromConfig(config.token)
: env.XAI_API_KEY, : env.XAI_API_KEY,
headers: config.headers headers: config.headers
? await extractLanguageModelKeyValuePairs(config.headers, orgId, prisma) ? await extractLanguageModelKeyValuePairs(config.headers)
: undefined, : undefined,
}); });
@ -617,9 +617,7 @@ export const _getAISDKLanguageModelAndOptions = async (config: LanguageModel, or
const extractLanguageModelKeyValuePairs = async ( const extractLanguageModelKeyValuePairs = async (
pairs: { pairs: {
[k: string]: string | Token; [k: string]: string | Token;
}, }
orgId: number,
db: PrismaClient,
): Promise<Record<string, string>> => { ): Promise<Record<string, string>> => {
const resolvedPairs: Record<string, string> = {}; const resolvedPairs: Record<string, string> = {};
@ -633,7 +631,7 @@ const extractLanguageModelKeyValuePairs = async (
continue; continue;
} }
const value = await getTokenFromConfig(val, orgId, db); const value = await getTokenFromConfig(val);
resolvedPairs[key] = value; resolvedPairs[key] = value;
} }

View file

@ -1,7 +0,0 @@
export const strings = {
connectionConfigDescription: "Configure what repositories, organizations, users, etc. you want to sync with Sourcebot. Use the quick actions below to help you configure your connection.",
createSecretDescription: "Secrets are used to authenticate with the code host, allowing Sourcebot to access private repositories.",
}
export default strings;

View file

@ -76,7 +76,6 @@
"$ref": "#/definitions/Token", "$ref": "#/definitions/Token",
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT).",
"examples": [ "examples": [
"secret-token",
{ "env": "ENV_VAR_CONTAINING_TOKEN" } { "env": "ENV_VAR_CONTAINING_TOKEN" }
] ]
}, },
@ -210,7 +209,6 @@
"$ref": "#/definitions/Token", "$ref": "#/definitions/Token",
"description": "An authentication token.", "description": "An authentication token.",
"examples": [ "examples": [
"secret-token",
{ "env": "ENV_VAR_CONTAINING_TOKEN" } { "env": "ENV_VAR_CONTAINING_TOKEN" }
] ]
}, },
@ -332,7 +330,6 @@
"$ref": "#/definitions/Token", "$ref": "#/definitions/Token",
"description": "An access token.", "description": "An access token.",
"examples": [ "examples": [
"secret-token",
{ "env": "ENV_VAR_CONTAINING_TOKEN" } { "env": "ENV_VAR_CONTAINING_TOKEN" }
] ]
}, },

View file

@ -9,12 +9,7 @@
}, },
"token": { "token": {
"$ref": "./shared.json#/definitions/Token", "$ref": "./shared.json#/definitions/Token",
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT)."
"examples": [
{
"secret": "SECRET_KEY"
}
]
}, },
"url": { "url": {
"type": "string", "type": "string",

View file

@ -13,12 +13,7 @@
}, },
"token": { "token": {
"$ref": "./shared.json#/definitions/Token", "$ref": "./shared.json#/definitions/Token",
"description": "An authentication token.", "description": "An authentication token."
"examples": [
{
"secret": "SECRET_KEY"
}
]
}, },
"url": { "url": {
"type": "string", "type": "string",

View file

@ -9,12 +9,7 @@
}, },
"token": { "token": {
"$ref": "./shared.json#/definitions/Token", "$ref": "./shared.json#/definitions/Token",
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT)."
"examples": [
{
"secret": "SECRET_KEY"
}
]
}, },
"url": { "url": {
"type": "string", "type": "string",

View file

@ -9,12 +9,7 @@
}, },
"token": { "token": {
"$ref": "./shared.json#/definitions/Token", "$ref": "./shared.json#/definitions/Token",
"description": "A Personal Access Token (PAT).", "description": "A Personal Access Token (PAT)."
"examples": [
{
"secret": "SECRET_KEY"
}
]
}, },
"url": { "url": {
"type": "string", "type": "string",

View file

@ -9,12 +9,7 @@
}, },
"token": { "token": {
"$ref": "./shared.json#/definitions/Token", "$ref": "./shared.json#/definitions/Token",
"description": "An authentication token.", "description": "An authentication token."
"examples": [
{
"secret": "SECRET_KEY"
}
]
}, },
"url": { "url": {
"type": "string", "type": "string",

View file

@ -4,19 +4,6 @@
"definitions": { "definitions": {
"Token": { "Token": {
"anyOf": [ "anyOf": [
{
"type": "object",
"properties": {
"secret": {
"type": "string",
"description": "The name of the secret that contains the token."
}
},
"required": [
"secret"
],
"additionalProperties": false
},
{ {
"type": "object", "type": "object",
"properties": { "properties": {