mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 11:28:18 +00:00
Fix merge conflicts
This commit is contained in:
@@ -47,11 +47,13 @@ CLIENT_ID_VERCEL=
|
|||||||
CLIENT_ID_NETLIFY=
|
CLIENT_ID_NETLIFY=
|
||||||
CLIENT_ID_GITHUB=
|
CLIENT_ID_GITHUB=
|
||||||
CLIENT_ID_GITLAB=
|
CLIENT_ID_GITLAB=
|
||||||
|
CLIENT_ID_BITBUCKET=
|
||||||
CLIENT_SECRET_HEROKU=
|
CLIENT_SECRET_HEROKU=
|
||||||
CLIENT_SECRET_VERCEL=
|
CLIENT_SECRET_VERCEL=
|
||||||
CLIENT_SECRET_NETLIFY=
|
CLIENT_SECRET_NETLIFY=
|
||||||
CLIENT_SECRET_GITHUB=
|
CLIENT_SECRET_GITHUB=
|
||||||
CLIENT_SECRET_GITLAB=
|
CLIENT_SECRET_GITLAB=
|
||||||
|
CLIENT_SECRET_BITBUCKET=
|
||||||
CLIENT_SLUG_VERCEL=
|
CLIENT_SLUG_VERCEL=
|
||||||
|
|
||||||
# Sentry (optional) for monitoring errors
|
# Sentry (optional) for monitoring errors
|
||||||
|
|||||||
+8
-8
@@ -1,9 +1,9 @@
|
|||||||
secretScanningGitApp:
|
# secretScanningGitApp:
|
||||||
enabled: true
|
# enabled: false
|
||||||
deploymentAnnotations:
|
# deploymentAnnotations:
|
||||||
secrets.infisical.com/auto-reload: "true"
|
# secrets.infisical.com/auto-reload: "true"
|
||||||
image:
|
# image:
|
||||||
repository: infisical/staging_deployment_secret-scanning-git-app
|
# repository: infisical/staging_deployment_secret-scanning-git-app
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -58,8 +58,8 @@ mongodbConnection:
|
|||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
annotations:
|
# annotations:
|
||||||
kubernetes.io/ingress.class: "nginx"
|
# kubernetes.io/ingress.class: "nginx"
|
||||||
# cert-manager.io/issuer: letsencrypt-nginx
|
# cert-manager.io/issuer: letsencrypt-nginx
|
||||||
hostName: gamma.infisical.com ## <- Replace with your own domain
|
hostName: gamma.infisical.com ## <- Replace with your own domain
|
||||||
frontend:
|
frontend:
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ jobs:
|
|||||||
- name: Download helm values to file and upgrade gamma deploy
|
- name: Download helm values to file and upgrade gamma deploy
|
||||||
run: |
|
run: |
|
||||||
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
|
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
|
||||||
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --wait
|
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --wait --install
|
||||||
if [[ $(helm status infisical) == *"FAILED"* ]]; then
|
if [[ $(helm status infisical) == *"FAILED"* ]]; then
|
||||||
echo "Helm upgrade failed"
|
echo "Helm upgrade failed"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
name: Release standalone docker image
|
name: Release standalone docker image
|
||||||
on: [workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "infisical/v*.*.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
infisical-standalone:
|
infisical-standalone:
|
||||||
name: Build infisical standalone image
|
name: Build infisical standalone image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: extract_version
|
||||||
|
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
|
||||||
- name: ☁️ Checkout source
|
- name: ☁️ Checkout source
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -64,5 +70,6 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
infisical/infisical:latest
|
infisical/infisical:latest
|
||||||
infisical/infisical:${{ steps.commit.outputs.short }}
|
infisical/infisical:${{ steps.commit.outputs.short }}
|
||||||
|
infisical/infisical:${{ steps.extract_version.outputs.version }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
file: Dockerfile.standalone-infisical
|
file: Dockerfile.standalone-infisical
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
name: Release Docker image for K8 operator
|
name: Release Docker image for K8 operator
|
||||||
on: [workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "infisical-k8-operator/v*.*.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: extract_version
|
||||||
|
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: 🔧 Set up QEMU
|
- name: 🔧 Set up QEMU
|
||||||
@@ -26,4 +32,6 @@ jobs:
|
|||||||
context: k8-operator
|
context: k8-operator
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: infisical/kubernetes-operator:latest
|
tags: |
|
||||||
|
infisical/kubernetes-operator:latest
|
||||||
|
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
||||||
|
|||||||
@@ -108,6 +108,22 @@ brews:
|
|||||||
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||||
fish_completion.install "completions/infisical.fish"
|
fish_completion.install "completions/infisical.fish"
|
||||||
man1.install "manpages/infisical.1.gz"
|
man1.install "manpages/infisical.1.gz"
|
||||||
|
- name: 'infisical@{{.Version}}'
|
||||||
|
tap:
|
||||||
|
owner: Infisical
|
||||||
|
name: homebrew-get-cli
|
||||||
|
commit_author:
|
||||||
|
name: "Infisical"
|
||||||
|
email: [email protected]
|
||||||
|
folder: Formula
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
install: |-
|
||||||
|
bin.install "infisical"
|
||||||
|
bash_completion.install "completions/infisical.bash" => "infisical"
|
||||||
|
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||||
|
fish_completion.install "completions/infisical.fish"
|
||||||
|
man1.install "manpages/infisical.1.gz"
|
||||||
|
|
||||||
nfpms:
|
nfpms:
|
||||||
- id: infisical
|
- id: infisical
|
||||||
|
|||||||
+2
-6
@@ -10,6 +10,7 @@
|
|||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-empty-function": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"no-console": 2,
|
"no-console": 2,
|
||||||
"quotes": [
|
"quotes": [
|
||||||
@@ -34,11 +35,6 @@
|
|||||||
"argsIgnorePattern": "^_"
|
"argsIgnorePattern": "^_"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort-imports": [
|
"sort-imports": 1
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignoreDeclarationSort": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,10 @@ RUN npm ci --only-production
|
|||||||
|
|
||||||
COPY --from=build /app .
|
COPY --from=build /app .
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||||
|
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||||
|
&& apk add infisical=0.8.1
|
||||||
|
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||||
CMD node healthcheck.js
|
CMD node healthcheck.js
|
||||||
|
|
||||||
|
|||||||
Generated
+6096
-2910
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
||||||
"@godaddy/terminus": "^4.12.0",
|
"@godaddy/terminus": "^4.12.0",
|
||||||
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
"@sentry/node": "^7.49.0",
|
"@sentry/node": "^7.49.0",
|
||||||
"@sentry/tracing": "^7.48.0",
|
"@sentry/tracing": "^7.48.0",
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
|
"probot": "^12.3.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"rate-limit-mongo": "^2.3.2",
|
"rate-limit-mongo": "^2.3.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
@@ -89,6 +91,7 @@
|
|||||||
"@types/node": "^18.11.3",
|
"@types/node": "^18.11.3",
|
||||||
"@types/nodemailer": "^6.4.6",
|
"@types/nodemailer": "^6.4.6",
|
||||||
"@types/passport": "^1.0.12",
|
"@types/passport": "^1.0.12",
|
||||||
|
"@types/picomatch": "^2.3.0",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"@types/swagger-jsdoc": "^6.0.1",
|
"@types/swagger-jsdoc": "^6.0.1",
|
||||||
"@types/swagger-ui-express": "^4.1.3",
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
@@ -102,6 +105,7 @@
|
|||||||
"jest-junit": "^15.0.0",
|
"jest-junit": "^15.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.19",
|
||||||
"npm": "^8.19.3",
|
"npm": "^8.19.3",
|
||||||
|
"smee-client": "^1.2.3",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.0.3",
|
"ts-jest": "^29.0.3",
|
||||||
"ts-node": "^10.9.1"
|
"ts-node": "^10.9.1"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const getEncryptionKey = async () => {
|
|||||||
return secretValue === "" ? undefined : secretValue;
|
return secretValue === "" ? undefined : secretValue;
|
||||||
}
|
}
|
||||||
export const getRootEncryptionKey = async () => {
|
export const getRootEncryptionKey = async () => {
|
||||||
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
|
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
|
||||||
return secretValue === "" ? undefined : secretValue;
|
return secretValue === "" ? undefined : secretValue;
|
||||||
}
|
}
|
||||||
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
|
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
|
||||||
@@ -37,6 +37,7 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID
|
|||||||
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
|
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
|
||||||
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
|
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
|
||||||
export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
|
export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
|
||||||
|
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
|
||||||
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
|
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
|
||||||
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
|
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
|
||||||
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
|
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
|
||||||
@@ -44,6 +45,7 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIEN
|
|||||||
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
|
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
|
||||||
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
|
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
|
||||||
export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
|
export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
|
||||||
|
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
|
||||||
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
|
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
|
||||||
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
||||||
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
|
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
|
||||||
@@ -57,6 +59,11 @@ export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWOR
|
|||||||
export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
|
export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
|
||||||
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
|
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
|
||||||
|
|
||||||
|
export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue;
|
||||||
|
export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue;
|
||||||
|
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
|
||||||
|
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
|
||||||
|
|
||||||
export const getLicenseKey = async () => {
|
export const getLicenseKey = async () => {
|
||||||
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
|
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
|
||||||
return secretValue === "" ? undefined : secretValue;
|
return secretValue === "" ? undefined : secretValue;
|
||||||
|
|||||||
@@ -13,21 +13,27 @@ import * as signupController from "./signupController";
|
|||||||
import * as userActionController from "./userActionController";
|
import * as userActionController from "./userActionController";
|
||||||
import * as userController from "./userController";
|
import * as userController from "./userController";
|
||||||
import * as workspaceController from "./workspaceController";
|
import * as workspaceController from "./workspaceController";
|
||||||
|
import * as secretScanningController from "./secretScanningController";
|
||||||
|
import * as webhookController from "./webhookController";
|
||||||
|
import * as secretImportController from "./secretImportController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
botController,
|
botController,
|
||||||
integrationAuthController,
|
integrationAuthController,
|
||||||
integrationController,
|
integrationController,
|
||||||
keyController,
|
keyController,
|
||||||
membershipController,
|
membershipController,
|
||||||
membershipOrgController,
|
membershipOrgController,
|
||||||
organizationController,
|
organizationController,
|
||||||
passwordController,
|
passwordController,
|
||||||
secretController,
|
secretController,
|
||||||
serviceTokenController,
|
serviceTokenController,
|
||||||
signupController,
|
signupController,
|
||||||
userActionController,
|
userActionController,
|
||||||
userController,
|
userController,
|
||||||
workspaceController,
|
workspaceController,
|
||||||
|
secretScanningController,
|
||||||
|
webhookController,
|
||||||
|
secretImportController
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { IntegrationService } from "../../services";
|
|||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
|
INTEGRATION_BITBUCKET_API_URL,
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
@@ -141,12 +142,14 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
|||||||
*/
|
*/
|
||||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||||
const teamId = req.query.teamId as string;
|
const teamId = req.query.teamId as string;
|
||||||
|
const workspaceSlug = req.query.workspaceSlug as string;
|
||||||
|
|
||||||
const apps = await getApps({
|
const apps = await getApps({
|
||||||
integrationAuth: req.integrationAuth,
|
integrationAuth: req.integrationAuth,
|
||||||
accessToken: req.accessToken,
|
accessToken: req.accessToken,
|
||||||
accessId: req.accessId,
|
accessId: req.accessId,
|
||||||
...(teamId && { teamId })
|
...(teamId && { teamId }),
|
||||||
|
...(workspaceSlug && { workspaceSlug })
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@@ -382,6 +385,66 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of workspaces allowed for Bitbucket integration
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => {
|
||||||
|
|
||||||
|
interface WorkspaceResponse {
|
||||||
|
size: number;
|
||||||
|
page: number;
|
||||||
|
pageLen: number;
|
||||||
|
next: string;
|
||||||
|
previous: string;
|
||||||
|
values: Array<Workspace>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Workspace {
|
||||||
|
type: string;
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
is_private: boolean;
|
||||||
|
created_on: string;
|
||||||
|
updated_on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaces: Workspace[] = [];
|
||||||
|
let hasNextPage = true;
|
||||||
|
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`
|
||||||
|
|
||||||
|
while (hasNextPage) {
|
||||||
|
const { data }: { data: WorkspaceResponse } = await standardRequest.get(
|
||||||
|
workspaceUrl,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${req.accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data?.values.length > 0) {
|
||||||
|
data.values.forEach((workspace) => {
|
||||||
|
workspaces.push(workspace)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.next) {
|
||||||
|
workspaceUrl = data.next
|
||||||
|
} else {
|
||||||
|
hasNextPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspaces
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete integration authorization with id [integrationAuthId]
|
* Delete integration authorization with id [integrationAuthId]
|
||||||
* @param req
|
* @param req
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Integration } from "../../models";
|
import { Integration } from "../../models";
|
||||||
import { EventService } from "../../services";
|
import { EventService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventStartIntegration } from "../../events";
|
||||||
import Folder from "../../models/folder";
|
import Folder from "../../models/folder";
|
||||||
import { getFolderByPath } from "../../services/FolderService";
|
import { getFolderByPath } from "../../services/FolderService";
|
||||||
import { BadRequestError } from "../../utils/errors";
|
import { BadRequestError } from "../../utils/errors";
|
||||||
@@ -27,19 +27,19 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region,
|
region,
|
||||||
secretPath,
|
secretPath
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
const folders = await Folder.findOne({
|
||||||
workspace: req.integrationAuth.workspace._id,
|
workspace: req.integrationAuth.workspace._id,
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment
|
||||||
});
|
});
|
||||||
|
|
||||||
if (folders) {
|
if (folders) {
|
||||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message: "Path for service token does not exist",
|
message: "Path for service token does not exist"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,21 +62,21 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
region,
|
region,
|
||||||
secretPath,
|
secretPath,
|
||||||
integration: req.integrationAuth.integration,
|
integration: req.integrationAuth.integration,
|
||||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
if (integration) {
|
if (integration) {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
EventService.handleEvent({
|
EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventStartIntegration({
|
||||||
workspaceId: integration.workspace,
|
workspaceId: integration.workspace,
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration,
|
integration
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,26 +97,26 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
|||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner, // github-specific integration param
|
owner, // github-specific integration param
|
||||||
secretPath,
|
secretPath
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
const folders = await Folder.findOne({
|
||||||
workspace: req.integration.workspace,
|
workspace: req.integration.workspace,
|
||||||
environment,
|
environment
|
||||||
});
|
});
|
||||||
|
|
||||||
if (folders) {
|
if (folders) {
|
||||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message: "Path for service token does not exist",
|
message: "Path for service token does not exist"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const integration = await Integration.findOneAndUpdate(
|
const integration = await Integration.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: req.integration._id,
|
_id: req.integration._id
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
environment,
|
environment,
|
||||||
@@ -125,25 +125,25 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
|||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner,
|
owner,
|
||||||
secretPath,
|
secretPath
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (integration) {
|
if (integration) {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
EventService.handleEvent({
|
EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventStartIntegration({
|
||||||
workspaceId: integration.workspace,
|
workspaceId: integration.workspace,
|
||||||
environment,
|
environment
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration,
|
integration
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,12 +158,12 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
|||||||
const { integrationId } = req.params;
|
const { integrationId } = req.params;
|
||||||
|
|
||||||
const integration = await Integration.findOneAndDelete({
|
const integration = await Integration.findOneAndDelete({
|
||||||
_id: integrationId,
|
_id: integrationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integration) throw new Error("Failed to find integration");
|
if (!integration) throw new Error("Failed to find integration");
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration,
|
integration
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { MembershipOrg, Organization, User } from "../../models";
|
import { MembershipOrg, Organization, User } from "../../models";
|
||||||
|
import { SSOConfig } from "../../ee/models";
|
||||||
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
|
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
|
||||||
import { createToken } from "../../helpers/auth";
|
import { createToken } from "../../helpers/auth";
|
||||||
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||||
@@ -110,6 +111,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(organizationId);
|
const plan = await EELicenseService.getPlan(organizationId);
|
||||||
|
|
||||||
|
const ssoConfig = await SSOConfig.findOne({
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ssoConfig && ssoConfig.isActive) {
|
||||||
|
// case: SAML SSO is enabled for the organization
|
||||||
|
return res.status(400).send({
|
||||||
|
message:
|
||||||
|
"Failed to invite member due to SAML SSO configured for organization"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (plan.memberLimit !== null) {
|
if (plan.memberLimit !== null) {
|
||||||
// case: limit imposed on number of members allowed
|
// case: limit imposed on number of members allowed
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { createOrganization as create } from "../../helpers/organization";
|
import { createOrganization as create } from "../../helpers/organization";
|
||||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||||
import { ACCEPTED, OWNER } from "../../variables";
|
import { ACCEPTED, OWNER } from "../../variables";
|
||||||
import { getSiteURL, getLicenseServerUrl } from "../../config";
|
import { getLicenseServerUrl, getSiteURL } from "../../config";
|
||||||
import { licenseServerKeyRequest } from "../../config/request";
|
import { licenseServerKeyRequest } from "../../config/request";
|
||||||
|
|
||||||
export const getOrganizations = async (req: Request, res: Response) => {
|
export const getOrganizations = async (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
|||||||
EventService.handleEvent({
|
EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment
|
environment,
|
||||||
|
secretPath: "/"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { validateMembership } from "../../helpers";
|
||||||
|
import SecretImport from "../../models/secretImports";
|
||||||
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
|
import { BadRequestError } from "../../utils/errors";
|
||||||
|
import { ADMIN, MEMBER } from "../../variables";
|
||||||
|
|
||||||
|
export const createSecretImport = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId, environment, folderId, secretImport } = req.body;
|
||||||
|
const importSecDoc = await SecretImport.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!importSecDoc) {
|
||||||
|
const doc = new SecretImport({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId,
|
||||||
|
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
|
||||||
|
});
|
||||||
|
await doc.save();
|
||||||
|
return res.status(200).json({ message: "successfully created secret import" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesImportExist = importSecDoc.imports.find(
|
||||||
|
(el) => el.environment === secretImport.environment && el.secretPath === secretImport.secretPath
|
||||||
|
);
|
||||||
|
if (doesImportExist) {
|
||||||
|
throw BadRequestError({ message: "Secret import already exist" });
|
||||||
|
}
|
||||||
|
importSecDoc.imports.push({
|
||||||
|
environment: secretImport.environment,
|
||||||
|
secretPath: secretImport.secretPath
|
||||||
|
});
|
||||||
|
await importSecDoc.save();
|
||||||
|
return res.status(200).json({ message: "successfully created secret import" });
|
||||||
|
};
|
||||||
|
|
||||||
|
// to keep the ordering, you must pass all the imports in here not the only updated one
|
||||||
|
// this is because the order decide which import gets overriden
|
||||||
|
export const updateSecretImport = async (req: Request, res: Response) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { secretImports } = req.body;
|
||||||
|
const importSecDoc = await SecretImport.findById(id);
|
||||||
|
if (!importSecDoc) {
|
||||||
|
throw BadRequestError({ message: "Import not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: importSecDoc.workspace,
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
});
|
||||||
|
|
||||||
|
importSecDoc.imports = secretImports;
|
||||||
|
await importSecDoc.save();
|
||||||
|
return res.status(200).json({ message: "successfully updated secret import" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSecretImport = async (req: Request, res: Response) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { secretImportEnv, secretImportPath } = req.body;
|
||||||
|
const importSecDoc = await SecretImport.findById(id);
|
||||||
|
if (!importSecDoc) {
|
||||||
|
throw BadRequestError({ message: "Import not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: importSecDoc.workspace,
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
});
|
||||||
|
importSecDoc.imports = importSecDoc.imports.filter(
|
||||||
|
({ environment, secretPath }) =>
|
||||||
|
!(environment === secretImportEnv && secretPath === secretImportPath)
|
||||||
|
);
|
||||||
|
await importSecDoc.save();
|
||||||
|
return res.status(200).json({ message: "successfully delete secret import" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSecretImports = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId, environment, folderId } = req.query;
|
||||||
|
const importSecDoc = await SecretImport.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!importSecDoc) {
|
||||||
|
return res.status(200).json({ secretImport: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ secretImport: importSecDoc });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId, environment, folderId } = req.query as {
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
folderId: string;
|
||||||
|
};
|
||||||
|
const importSecDoc = await SecretImport.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!importSecDoc) {
|
||||||
|
return res.status(200).json({ secrets: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
const secrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||||
|
return res.status(200).json({ secrets });
|
||||||
|
};
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import GitAppInstallationSession from "../../models/gitAppInstallationSession";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||||
|
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||||
|
import { MembershipOrg } from "../../models";
|
||||||
|
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../models/gitRisks";
|
||||||
|
|
||||||
|
export const createInstallationSession = async (req: Request, res: Response) => {
|
||||||
|
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||||
|
await GitAppInstallationSession.findByIdAndUpdate(
|
||||||
|
req.organization,
|
||||||
|
{
|
||||||
|
organization: new Types.ObjectId(req.organization),
|
||||||
|
sessionId: sessionId,
|
||||||
|
user: new Types.ObjectId(req.user._id)
|
||||||
|
},
|
||||||
|
{ upsert: true }
|
||||||
|
).lean();
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
sessionId: sessionId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const linkInstallationToOrganization = async (req: Request, res: Response) => {
|
||||||
|
const { installationId, sessionId } = req.body
|
||||||
|
|
||||||
|
const installationSession = await GitAppInstallationSession.findOneAndDelete({ sessionId: sessionId })
|
||||||
|
if (!installationSession) {
|
||||||
|
throw UnauthorizedRequestError()
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization })
|
||||||
|
if (!userMembership) {
|
||||||
|
throw UnauthorizedRequestError()
|
||||||
|
}
|
||||||
|
|
||||||
|
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({
|
||||||
|
organizationId: installationSession.organization,
|
||||||
|
}, {
|
||||||
|
installationId: installationId,
|
||||||
|
organizationId: installationSession.organization,
|
||||||
|
user: installationSession.user
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
res.json(installationLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params
|
||||||
|
try {
|
||||||
|
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean()
|
||||||
|
if (!appInstallation) {
|
||||||
|
res.json({
|
||||||
|
appInstallationComplete: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
appInstallationComplete: true
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
res.json({
|
||||||
|
appInstallationComplete: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params
|
||||||
|
const risks = await GitRisks.find({ organization: organizationId }).sort({ createdAt: -1 }).lean()
|
||||||
|
res.json({
|
||||||
|
risks: risks
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateRisksStatus = async (req: Request, res: Response) => {
|
||||||
|
const { riskId } = req.params
|
||||||
|
const { status } = req.body
|
||||||
|
const isRiskResolved = status == STATUS_RESOLVED_FALSE_POSITIVE || status == STATUS_RESOLVED_REVOKED || status == STATUS_RESOLVED_NOT_REVOKED ? true : false
|
||||||
|
const risk = await GitRisks.findByIdAndUpdate(riskId, {
|
||||||
|
status: status,
|
||||||
|
isResolved: isRiskResolved
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
res.json(risk)
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { client, getRootEncryptionKey } from "../../config";
|
||||||
|
import { validateMembership } from "../../helpers";
|
||||||
|
import Webhook from "../../models/webhooks";
|
||||||
|
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
|
||||||
|
import { BadRequestError } from "../../utils/errors";
|
||||||
|
import { ADMIN, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, MEMBER } from "../../variables";
|
||||||
|
|
||||||
|
export const createWebhook = async (req: Request, res: Response) => {
|
||||||
|
const { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } = req.body;
|
||||||
|
const webhook = new Webhook({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
url: webhookUrl,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
});
|
||||||
|
|
||||||
|
if (webhookSecretKey) {
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
|
||||||
|
webhook.iv = iv;
|
||||||
|
webhook.tag = tag;
|
||||||
|
webhook.encryptedSecretKey = ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
await webhook.save();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
webhook,
|
||||||
|
message: "successfully created webhook"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateWebhook = async (req: Request, res: Response) => {
|
||||||
|
const { webhookId } = req.params;
|
||||||
|
const { isDisabled } = req.body;
|
||||||
|
const webhook = await Webhook.findById(webhookId);
|
||||||
|
if (!webhook) {
|
||||||
|
throw BadRequestError({ message: "Webhook not found!!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that user is a member of the workspace
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: webhook.workspace,
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof isDisabled !== undefined) {
|
||||||
|
webhook.isDisabled = isDisabled;
|
||||||
|
}
|
||||||
|
await webhook.save();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
webhook,
|
||||||
|
message: "successfully updated webhook"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteWebhook = async (req: Request, res: Response) => {
|
||||||
|
const { webhookId } = req.params;
|
||||||
|
const webhook = await Webhook.findById(webhookId);
|
||||||
|
if (!webhook) {
|
||||||
|
throw BadRequestError({ message: "Webhook not found!!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: webhook.workspace,
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
});
|
||||||
|
await webhook.remove();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: "successfully removed webhook"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testWebhook = async (req: Request, res: Response) => {
|
||||||
|
const { webhookId } = req.params;
|
||||||
|
const webhook = await Webhook.findById(webhookId);
|
||||||
|
if (!webhook) {
|
||||||
|
throw BadRequestError({ message: "Webhook not found!!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: webhook.workspace,
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await triggerWebhookRequest(
|
||||||
|
webhook,
|
||||||
|
getWebhookPayload(
|
||||||
|
"test",
|
||||||
|
webhook.workspace.toString(),
|
||||||
|
webhook.environment,
|
||||||
|
webhook.secretPath
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await Webhook.findByIdAndUpdate(webhookId, {
|
||||||
|
lastStatus: "success",
|
||||||
|
lastRunErrorMessage: null
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
await Webhook.findByIdAndUpdate(webhookId, {
|
||||||
|
lastStatus: "failed",
|
||||||
|
lastRunErrorMessage: (err as Error).message
|
||||||
|
});
|
||||||
|
return res.status(400).send({
|
||||||
|
message: "Failed to receive response",
|
||||||
|
error: (err as Error).message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: "Successfully received response"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listWebhooks = async (req: Request, res: Response) => {
|
||||||
|
const { environment, workspaceId, secretPath } = req.query;
|
||||||
|
|
||||||
|
const optionalFilters: Record<string, string> = {};
|
||||||
|
if (environment) optionalFilters.environment = environment as string;
|
||||||
|
if (secretPath) optionalFilters.secretPath = secretPath as string;
|
||||||
|
|
||||||
|
const webhooks = await Webhook.find({
|
||||||
|
workspace: new Types.ObjectId(workspaceId as string),
|
||||||
|
...optionalFilters
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
webhooks
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -27,16 +27,16 @@ export const createWorkspaceEnvironment = async (
|
|||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
const { environmentName, environmentSlug } = req.body;
|
const { environmentName, environmentSlug } = req.body;
|
||||||
const workspace = await Workspace.findById(workspaceId).exec();
|
const workspace = await Workspace.findById(workspaceId).exec();
|
||||||
|
|
||||||
if (!workspace) throw WorkspaceNotFoundError();
|
if (!workspace) throw WorkspaceNotFoundError();
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(workspace.organization.toString());
|
const plan = await EELicenseService.getPlan(workspace.organization.toString());
|
||||||
|
|
||||||
if (plan.environmentLimit !== null) {
|
if (plan.environmentLimit !== null) {
|
||||||
// case: limit imposed on number of environments allowed
|
// case: limit imposed on number of environments allowed
|
||||||
if (workspace.environments.length >= plan.environmentLimit) {
|
if (workspace.environments.length >= plan.environmentLimit) {
|
||||||
// case: number of environments used exceeds the number of environments allowed
|
// case: number of environments used exceeds the number of environments allowed
|
||||||
|
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: "Failed to create environment due to environment limit reached. Upgrade plan to create more environments.",
|
message: "Failed to create environment due to environment limit reached. Upgrade plan to create more environments.",
|
||||||
});
|
});
|
||||||
@@ -191,14 +191,21 @@ export const deleteWorkspaceEnvironment = async (
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment: environmentSlug,
|
environment: environmentSlug,
|
||||||
});
|
});
|
||||||
await ServiceToken.deleteMany({
|
|
||||||
workspace: workspaceId,
|
// await ServiceToken.deleteMany({
|
||||||
environment: environmentSlug,
|
// workspace: workspaceId,
|
||||||
});
|
// environment: environmentSlug,
|
||||||
await ServiceTokenData.deleteMany({
|
// });
|
||||||
workspace: workspaceId,
|
|
||||||
environment: environmentSlug,
|
const result = await ServiceTokenData.updateMany(
|
||||||
});
|
{ workspace: workspaceId },
|
||||||
|
{ $pull: { scopes: { environment: environmentSlug } } }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.modifiedCount > 0) {
|
||||||
|
await ServiceTokenData.deleteMany({ workspace: workspaceId, scopes: { $size: 0 } });
|
||||||
|
}
|
||||||
|
|
||||||
await Integration.deleteMany({
|
await Integration.deleteMany({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment: environmentSlug,
|
environment: environmentSlug,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import {
|
|||||||
} from "../../types/secret";
|
} from "../../types/secret";
|
||||||
const { ValidationError } = mongoose.Error;
|
const { ValidationError } = mongoose.Error;
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
|
||||||
InternalServerError,
|
|
||||||
ValidationError as RouteValidationError,
|
ValidationError as RouteValidationError,
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from "../../utils/errors";
|
} from "../../utils/errors";
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
ACTION_UPDATE_SECRETS,
|
ACTION_UPDATE_SECRETS,
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import { EventService } from "../../services";
|
import { EventService } from "../../services";
|
||||||
@@ -21,7 +21,7 @@ import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
|||||||
import {
|
import {
|
||||||
userHasNoAbility,
|
userHasNoAbility,
|
||||||
userHasWorkspaceAccess,
|
userHasWorkspaceAccess,
|
||||||
userHasWriteOnlyAbility,
|
userHasWriteOnlyAbility
|
||||||
} from "../../ee/helpers/checkMembershipPermissions";
|
} from "../../ee/helpers/checkMembershipPermissions";
|
||||||
import Tag from "../../models/tag";
|
import Tag from "../../models/tag";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -31,7 +31,11 @@ import {
|
|||||||
getFolderByPath,
|
getFolderByPath,
|
||||||
getFolderIdFromServiceToken,
|
getFolderIdFromServiceToken,
|
||||||
searchByFolderId,
|
searchByFolderId,
|
||||||
|
searchByFolderIdWithDir
|
||||||
} from "../../services/FolderService";
|
} from "../../services/FolderService";
|
||||||
|
import { isValidScope } from "../../helpers/secrets";
|
||||||
|
import path from "path";
|
||||||
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Peform a batch of any specified CUD secret operations
|
* Peform a batch of any specified CUD secret operations
|
||||||
@@ -46,14 +50,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
requests,
|
requests
|
||||||
secretPath,
|
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
requests: BatchSecretRequest[];
|
requests: BatchSecretRequest[];
|
||||||
secretPath: string;
|
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
let secretPath = req.body.secretPath as string;
|
||||||
let folderId = req.body.folderId as string;
|
let folderId = req.body.folderId as string;
|
||||||
|
|
||||||
const createSecrets: BatchSecret[] = [];
|
const createSecrets: BatchSecret[] = [];
|
||||||
@@ -63,31 +66,31 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// get secret blind index salt
|
// get secret blind index salt
|
||||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
if (folders && folderId !== "root") {
|
|
||||||
const folder = searchByFolderId(folders.nodes, folderId as string);
|
|
||||||
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
||||||
|
|
||||||
// in service token when not giving secretpath folderid must be root
|
// in service token when not giving secretpath folderid must be root
|
||||||
// this is to avoid giving folderid when service tokens are used
|
// this is to avoid giving folderid when service tokens are used
|
||||||
if (
|
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
|
||||||
(!secretPath && folderId !== "root") ||
|
|
||||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
|
||||||
) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secretPath) {
|
if (secretPath) {
|
||||||
folderId = await getFolderIdFromServiceToken(
|
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
}
|
||||||
environment,
|
|
||||||
secretPath
|
if (folders && folderId !== "root") {
|
||||||
|
const folder = searchByFolderIdWithDir(folders.nodes, folderId as string);
|
||||||
|
if (!folder?.folder) throw BadRequestError({ message: "Folder not found" });
|
||||||
|
secretPath = path.join(
|
||||||
|
"/",
|
||||||
|
...folder.dir.map(({ name }) => name).filter((name) => name !== "root")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,12 +100,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
let secretBlindIndex = "";
|
let secretBlindIndex = "";
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case "POST":
|
case "POST":
|
||||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt(
|
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||||
{
|
secretName: request.secret.secretName,
|
||||||
secretName: request.secret.secretName,
|
salt
|
||||||
salt,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
createSecrets.push({
|
createSecrets.push({
|
||||||
...request.secret,
|
...request.secret,
|
||||||
@@ -113,16 +114,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
folder: folderId,
|
folder: folderId,
|
||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "PATCH":
|
case "PATCH":
|
||||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt(
|
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||||
{
|
secretName: request.secret.secretName,
|
||||||
secretName: request.secret.secretName,
|
salt
|
||||||
salt,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
updateSecrets.push({
|
updateSecrets.push({
|
||||||
...request.secret,
|
...request.secret,
|
||||||
@@ -130,7 +129,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
@@ -150,9 +149,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
...n._doc,
|
...n._doc,
|
||||||
_id: new Types.ObjectId(),
|
_id: new Types.ObjectId(),
|
||||||
secret: n._id,
|
secret: n._id,
|
||||||
isDeleted: false,
|
isDeleted: false
|
||||||
};
|
};
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const addAction = (await EELogService.createAction({
|
const addAction = (await EELogService.createAction({
|
||||||
@@ -161,7 +160,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
serviceAccountId: req.serviceAccount?._id,
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
serviceTokenDataId: req.serviceTokenData?._id,
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: createdSecrets.map((n) => n._id),
|
secretIds: createdSecrets.map((n) => n._id)
|
||||||
})) as IAction;
|
})) as IAction;
|
||||||
actions.push(addAction);
|
actions.push(addAction);
|
||||||
|
|
||||||
@@ -175,8 +174,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel,
|
channel,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +194,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
listedSecretsObj = req.secrets.reduce(
|
listedSecretsObj = req.secrets.reduce(
|
||||||
(obj: any, secret: ISecret) => ({
|
(obj: any, secret: ISecret) => ({
|
||||||
...obj,
|
...obj,
|
||||||
[secret._id.toString()]: secret,
|
[secret._id.toString()]: secret
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
@@ -204,16 +203,16 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
updateOne: {
|
updateOne: {
|
||||||
filter: {
|
filter: {
|
||||||
_id: new Types.ObjectId(u._id),
|
_id: new Types.ObjectId(u._id),
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
$inc: {
|
$inc: {
|
||||||
version: 1,
|
version: 1
|
||||||
},
|
},
|
||||||
...u,
|
...u,
|
||||||
_id: new Types.ObjectId(u._id),
|
_id: new Types.ObjectId(u._id)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Secret.bulkWrite(updateOperations);
|
await Secret.bulkWrite(updateOperations);
|
||||||
@@ -240,25 +239,25 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
tags: u.tags,
|
tags: u.tags,
|
||||||
folder: u.folder,
|
folder: u.folder
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await EESecretService.addSecretVersions({
|
await EESecretService.addSecretVersions({
|
||||||
secretVersions,
|
secretVersions
|
||||||
});
|
});
|
||||||
|
|
||||||
updatedSecrets = await Secret.find({
|
updatedSecrets = await Secret.find({
|
||||||
_id: {
|
_id: {
|
||||||
$in: updateSecrets.map((u) => new Types.ObjectId(u._id)),
|
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateAction = (await EELogService.createAction({
|
const updateAction = (await EELogService.createAction({
|
||||||
name: ACTION_UPDATE_SECRETS,
|
name: ACTION_UPDATE_SECRETS,
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: updatedSecrets.map((u) => u._id),
|
secretIds: updatedSecrets.map((u) => u._id)
|
||||||
})) as IAction;
|
})) as IAction;
|
||||||
actions.push(updateAction);
|
actions.push(updateAction);
|
||||||
|
|
||||||
@@ -272,8 +271,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel,
|
channel,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,19 +281,19 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
if (deleteSecrets.length > 0) {
|
if (deleteSecrets.length > 0) {
|
||||||
await Secret.deleteMany({
|
await Secret.deleteMany({
|
||||||
_id: {
|
_id: {
|
||||||
$in: deleteSecrets,
|
$in: deleteSecrets
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await EESecretService.markDeletedSecretVersions({
|
await EESecretService.markDeletedSecretVersions({
|
||||||
secretIds: deleteSecrets,
|
secretIds: deleteSecrets
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteAction = (await EELogService.createAction({
|
const deleteAction = (await EELogService.createAction({
|
||||||
name: ACTION_DELETE_SECRETS,
|
name: ACTION_DELETE_SECRETS,
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: deleteSecrets,
|
secretIds: deleteSecrets
|
||||||
})) as IAction;
|
})) as IAction;
|
||||||
actions.push(deleteAction);
|
actions.push(deleteAction);
|
||||||
|
|
||||||
@@ -307,8 +306,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,7 +319,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,14 +327,17 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
}),
|
environment,
|
||||||
|
// root condition else this will be filled according to the path or folderid
|
||||||
|
secretPath: secretPath || "/"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folderId,
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
const resObj: { [key: string]: ISecret[] | string[] } = {};
|
const resObj: { [key: string]: ISecret[] | string[] } = {};
|
||||||
@@ -418,7 +420,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
@@ -435,8 +437,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
);
|
);
|
||||||
if (!hasAccess) {
|
if (!hasAccess) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message:
|
message: "You do not have the necessary permission(s) perform this action"
|
||||||
"You do not have the necessary permission(s) perform this action",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,28 +450,27 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
// case: create 1 secret
|
// case: create 1 secret
|
||||||
listOfSecretsToCreate = [req.body.secrets];
|
listOfSecretsToCreate = [req.body.secrets];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
const isValidScopeAccess = isValidScope(
|
||||||
|
req.authData.authPayload,
|
||||||
|
environment,
|
||||||
|
secretPath || "/"
|
||||||
|
);
|
||||||
|
|
||||||
// in service token when not giving secretpath folderid must be root
|
// in service token when not giving secretpath folderid must be root
|
||||||
// this is to avoid giving folderid when service tokens are used
|
// this is to avoid giving folderid when service tokens are used
|
||||||
if (
|
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
|
||||||
(!secretPath && folderId !== "root") ||
|
|
||||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
|
||||||
) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (secretPath) {
|
if (secretPath) {
|
||||||
folderId = await getFolderIdFromServiceToken(
|
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get secret blind index salt
|
// get secret blind index salt
|
||||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
type secretsToCreateType = {
|
type secretsToCreateType = {
|
||||||
@@ -502,15 +502,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags,
|
tags
|
||||||
}: secretsToCreateType) => {
|
}: secretsToCreateType) => {
|
||||||
let secretBlindIndex;
|
let secretBlindIndex;
|
||||||
if (secretName) {
|
if (secretName) {
|
||||||
secretBlindIndex =
|
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||||
await SecretService.generateSecretBlindIndexWithSalt({
|
secretName,
|
||||||
secretName,
|
salt
|
||||||
salt,
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -532,22 +531,24 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
tags,
|
tags
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const newlyCreatedSecrets: ISecret[] = (
|
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map(
|
||||||
await Secret.insertMany(secretsToInsert)
|
(insertedSecret) => insertedSecret.toObject()
|
||||||
).map((insertedSecret) => insertedSecret.toObject());
|
);
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
}),
|
environment,
|
||||||
|
secretPath: secretPath || "/"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
@@ -567,7 +568,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag
|
||||||
}) =>
|
}) =>
|
||||||
new SecretVersion({
|
new SecretVersion({
|
||||||
secret: _id,
|
secret: _id,
|
||||||
@@ -586,9 +587,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretValueTag,
|
secretValueTag,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
})
|
})
|
||||||
),
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
const addAction = await EELogService.createAction({
|
const addAction = await EELogService.createAction({
|
||||||
@@ -597,7 +598,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
serviceAccountId: req.serviceAccount?._id,
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
serviceTokenDataId: req.serviceTokenData?._id,
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: newlyCreatedSecrets.map((n) => n._id),
|
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -609,14 +610,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
actions: [addAction],
|
actions: [addAction],
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folderId,
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -624,7 +625,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets added",
|
event: "secrets added",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: listOfSecretsToCreate.length,
|
numberOfSecrets: listOfSecretsToCreate.length,
|
||||||
@@ -632,13 +633,13 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
folderId,
|
folderId,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: newlyCreatedSecrets,
|
secrets: newlyCreatedSecrets
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -690,16 +691,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { tagSlugs, secretPath } = req.query;
|
const { tagSlugs, secretPath, include_imports } = req.query;
|
||||||
let { folderId } = req.query;
|
let { folderId } = req.query;
|
||||||
const workspaceId = req.query.workspaceId as string;
|
const workspaceId = req.query.workspaceId as string;
|
||||||
const environment = req.query.environment as string;
|
const environment = req.query.environment as string;
|
||||||
|
|
||||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
if (
|
if ((!folders && folderId && folderId !== "root") || (!folders && secretPath)) {
|
||||||
(!folders && folderId && folderId !== "root") ||
|
|
||||||
(!folders && secretPath)
|
|
||||||
) {
|
|
||||||
res.send({ secrets: [] });
|
res.send({ secrets: [] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -712,13 +710,15 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
const isValidScopeAccess = isValidScope(
|
||||||
|
req.authData.authPayload,
|
||||||
|
environment,
|
||||||
|
(secretPath as string) || "/"
|
||||||
|
);
|
||||||
|
|
||||||
// in service token when not giving secretpath folderid must be root
|
// in service token when not giving secretpath folderid must be root
|
||||||
// this is to avoid giving folderid when service tokens are used
|
// this is to avoid giving folderid when service tokens are used
|
||||||
if (
|
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
|
||||||
(!secretPath && folderId !== "root") ||
|
|
||||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
|
||||||
) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -738,8 +738,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// query tags table to get all tags ids for the tag names for the given workspace
|
// query tags table to get all tags ids for the tag names for the given workspace
|
||||||
let tagIds = [];
|
let tagIds = [];
|
||||||
const tagNamesList =
|
const tagNamesList = typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
|
||||||
typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
|
|
||||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId });
|
const workspaceFromDB = await Tag.find({ workspace: workspaceId });
|
||||||
tagIds = _.map(tagNamesList, (tagName: string) => {
|
tagIds = _.map(tagNamesList, (tagName: string) => {
|
||||||
@@ -762,8 +761,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
);
|
);
|
||||||
if (hasNoAccess) {
|
if (hasNoAccess) {
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message:
|
message: "You do not have the necessary permission(s) perform this action"
|
||||||
"You do not have the necessary permission(s) perform this action",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,8 +771,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
folder: folderId,
|
folder: folderId,
|
||||||
$or: [
|
$or: [
|
||||||
{ user: req.user._id }, // personal secrets for this user
|
{ user: req.user._id }, // personal secrets for this user
|
||||||
{ user: { $exists: false } }, // shared secrets from workspace
|
{ user: { $exists: false } } // shared secrets from workspace
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tagIds.length > 0) {
|
if (tagIds.length > 0) {
|
||||||
@@ -801,8 +799,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
$or: [
|
$or: [
|
||||||
{ user: userId }, // personal secrets for this user
|
{ user: userId }, // personal secrets for this user
|
||||||
{ user: { $exists: false } }, // shared secrets from workspace
|
{ user: { $exists: false } } // shared secrets from workspace
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tagIds.length > 0) {
|
if (tagIds.length > 0) {
|
||||||
@@ -820,7 +818,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
user: { $exists: false }, // shared secrets only from workspace
|
user: { $exists: false } // shared secrets only from workspace
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tagIds.length > 0) {
|
if (tagIds.length > 0) {
|
||||||
@@ -830,6 +828,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
secrets = await Secret.find(secretQuery).populate("tags");
|
secrets = await Secret.find(secretQuery).populate("tags");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(akhilmhdh) - secret-imp change this to org type
|
||||||
|
let importedSecrets: any[] = [];
|
||||||
|
if (include_imports === "true") {
|
||||||
|
importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string);
|
||||||
|
}
|
||||||
|
|
||||||
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
||||||
|
|
||||||
const readAction = await EELogService.createAction({
|
const readAction = await EELogService.createAction({
|
||||||
@@ -838,7 +842,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
serviceAccountId: req.serviceAccount?._id,
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
serviceTokenDataId: req.serviceTokenData?._id,
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||||
secretIds: secrets.map((n: any) => n._id),
|
secretIds: secrets.map((n: any) => n._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
readAction &&
|
readAction &&
|
||||||
@@ -849,7 +853,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||||
actions: [readAction],
|
actions: [readAction],
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -857,7 +861,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets pulled",
|
event: "secrets pulled",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
@@ -865,13 +869,14 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
channel,
|
channel,
|
||||||
folderId,
|
folderId,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets,
|
secrets,
|
||||||
|
...(include_imports && { imports: importedSecrets })
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -925,9 +930,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla")
|
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli";
|
||||||
? "web"
|
|
||||||
: "cli";
|
|
||||||
|
|
||||||
interface PatchSecret {
|
interface PatchSecret {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -943,51 +946,47 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
tags: string[];
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateOperationsToPerform = req.body.secrets.map(
|
const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
|
||||||
(secret: PatchSecret) => {
|
const {
|
||||||
const {
|
secretKeyCiphertext,
|
||||||
secretKeyCiphertext,
|
secretKeyIV,
|
||||||
secretKeyIV,
|
secretKeyTag,
|
||||||
secretKeyTag,
|
secretValueCiphertext,
|
||||||
secretValueCiphertext,
|
secretValueIV,
|
||||||
secretValueIV,
|
secretValueTag,
|
||||||
secretValueTag,
|
secretCommentCiphertext,
|
||||||
secretCommentCiphertext,
|
secretCommentIV,
|
||||||
secretCommentIV,
|
secretCommentTag,
|
||||||
secretCommentTag,
|
tags
|
||||||
tags,
|
} = secret;
|
||||||
} = secret;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateOne: {
|
updateOne: {
|
||||||
filter: { _id: new Types.ObjectId(secret.id) },
|
filter: { _id: new Types.ObjectId(secret.id) },
|
||||||
update: {
|
update: {
|
||||||
$inc: {
|
$inc: {
|
||||||
version: 1,
|
version: 1
|
||||||
},
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
|
||||||
tags,
|
|
||||||
...(secretCommentCiphertext !== undefined &&
|
|
||||||
secretCommentIV &&
|
|
||||||
secretCommentTag
|
|
||||||
? {
|
|
||||||
secretCommentCiphertext,
|
|
||||||
secretCommentIV,
|
|
||||||
secretCommentTag,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
},
|
||||||
},
|
secretKeyCiphertext,
|
||||||
};
|
secretKeyIV,
|
||||||
}
|
secretKeyTag,
|
||||||
);
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
|
tags,
|
||||||
|
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
|
||||||
|
? {
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
await Secret.bulkWrite(updateOperationsToPerform);
|
await Secret.bulkWrite(updateOperationsToPerform);
|
||||||
|
|
||||||
@@ -1009,7 +1008,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags,
|
tags
|
||||||
} = secretModificationsBySecretId[secret._id.toString()];
|
} = secretModificationsBySecretId[secret._id.toString()];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1018,9 +1017,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
workspace: secret.workspace,
|
workspace: secret.workspace,
|
||||||
type: secret.type,
|
type: secret.type,
|
||||||
environment: secret.environment,
|
environment: secret.environment,
|
||||||
secretKeyCiphertext: secretKeyCiphertext
|
secretKeyCiphertext: secretKeyCiphertext ? secretKeyCiphertext : secret.secretKeyCiphertext,
|
||||||
? secretKeyCiphertext
|
|
||||||
: secret.secretKeyCiphertext,
|
|
||||||
secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
|
secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
|
||||||
secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
|
secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
|
||||||
secretValueCiphertext: secretValueCiphertext
|
secretValueCiphertext: secretValueCiphertext
|
||||||
@@ -1031,17 +1028,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext: secretCommentCiphertext
|
secretCommentCiphertext: secretCommentCiphertext
|
||||||
? secretCommentCiphertext
|
? secretCommentCiphertext
|
||||||
: secret.secretCommentCiphertext,
|
: secret.secretCommentCiphertext,
|
||||||
secretCommentIV: secretCommentIV
|
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
||||||
? secretCommentIV
|
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
||||||
: secret.secretCommentIV,
|
|
||||||
secretCommentTag: secretCommentTag
|
|
||||||
? secretCommentTag
|
|
||||||
: secret.secretCommentTag,
|
|
||||||
tags: tags ? tags : secret.tags,
|
tags: tags ? tags : secret.tags,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
};
|
};
|
||||||
}),
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
await EESecretService.addSecretVersions(secretVersions);
|
await EESecretService.addSecretVersions(secretVersions);
|
||||||
@@ -1059,13 +1052,16 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
setTimeout(async () => {
|
// This route is not used anymore thus keep it commented out as it does not expose environment
|
||||||
await EventService.handleEvent({
|
// it will end up creating a lot of requests from the server
|
||||||
event: eventPushSecrets({
|
// setTimeout(async () => {
|
||||||
workspaceId: new Types.ObjectId(key),
|
// await EventService.handleEvent({
|
||||||
}),
|
// event: eventPushSecrets({
|
||||||
});
|
// workspaceId: new Types.ObjectId(key),
|
||||||
}, 10000);
|
// environment,
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// }, 10000);
|
||||||
|
|
||||||
const updateAction = await EELogService.createAction({
|
const updateAction = await EELogService.createAction({
|
||||||
name: ACTION_UPDATE_SECRETS,
|
name: ACTION_UPDATE_SECRETS,
|
||||||
@@ -1073,7 +1069,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
serviceAccountId: req.serviceAccount?._id,
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
serviceTokenDataId: req.serviceTokenData?._id,
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id),
|
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -1085,7 +1081,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
actions: [updateAction],
|
actions: [updateAction],
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
@@ -1101,15 +1097,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets modified",
|
event: "secrets modified",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: workspaceSecretObj[key].length,
|
numberOfSecrets: workspaceSecretObj[key].length,
|
||||||
environment: workspaceSecretObj[key][0].environment,
|
environment: workspaceSecretObj[key][0].environment,
|
||||||
workspaceId: key,
|
workspaceId: key,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1117,9 +1113,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: await Secret.find({
|
secrets: await Secret.find({
|
||||||
_id: {
|
_id: {
|
||||||
$in: req.secrets.map((secret: ISecret) => secret._id),
|
$in: req.secrets.map((secret: ISecret) => secret._id)
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1179,12 +1175,12 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
await Secret.deleteMany({
|
await Secret.deleteMany({
|
||||||
_id: {
|
_id: {
|
||||||
$in: toDelete,
|
$in: toDelete
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await EESecretService.markDeletedSecretVersions({
|
await EESecretService.markDeletedSecretVersions({
|
||||||
secretIds: toDelete,
|
secretIds: toDelete
|
||||||
});
|
});
|
||||||
|
|
||||||
// group secrets into workspaces so deleted secrets can
|
// group secrets into workspaces so deleted secrets can
|
||||||
@@ -1200,18 +1196,20 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
await EventService.handleEvent({
|
// DEPRECIATED(akhilmhdh): as this would cause server to send so many request
|
||||||
event: eventPushSecrets({
|
// and this route is not used anymore thus like snapshot keeping it commented out
|
||||||
workspaceId: new Types.ObjectId(key),
|
// await EventService.handleEvent({
|
||||||
}),
|
// event: eventPushSecrets({
|
||||||
});
|
// workspaceId: new Types.ObjectId(key)
|
||||||
|
// })
|
||||||
|
// });
|
||||||
const deleteAction = await EELogService.createAction({
|
const deleteAction = await EELogService.createAction({
|
||||||
name: ACTION_DELETE_SECRETS,
|
name: ACTION_DELETE_SECRETS,
|
||||||
userId: req.user?._id,
|
userId: req.user?._id,
|
||||||
serviceAccountId: req.serviceAccount?._id,
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
serviceTokenDataId: req.serviceTokenData?._id,
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id),
|
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -1223,7 +1221,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
actions: [deleteAction],
|
actions: [deleteAction],
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
@@ -1237,20 +1235,20 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets deleted",
|
event: "secrets deleted",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: workspaceSecretObj[key].length,
|
numberOfSecrets: workspaceSecretObj[key].length,
|
||||||
environment: workspaceSecretObj[key][0].environment,
|
environment: workspaceSecretObj[key][0].environment,
|
||||||
workspaceId: key,
|
workspaceId: key,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
userAgent: req.headers?.["user-agent"],
|
userAgent: req.headers?.["user-agent"]
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: req.secrets,
|
secrets: req.secrets
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,9 @@ import { Request, Response } from "express";
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import { ServiceAccount, ServiceTokenData, User } from "../../models";
|
import { ServiceAccount, ServiceTokenData, User } from "../../models";
|
||||||
import {
|
import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables";
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
|
||||||
} from "../../variables";
|
|
||||||
import { getSaltRounds } from "../../config";
|
import { getSaltRounds } from "../../config";
|
||||||
import { BadRequestError } from "../../utils/errors";
|
import { BadRequestError } from "../../utils/errors";
|
||||||
import Folder from "../../models/folder";
|
|
||||||
import { getFolderByPath } from "../../services/FolderService";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return service token data associated with service token on request
|
* Return service token data associated with service token on request
|
||||||
@@ -46,14 +41,13 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
if (!(req.authData.authPayload instanceof ServiceTokenData))
|
if (!(req.authData.authPayload instanceof ServiceTokenData))
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message: "Failed accepted client validation for service token data",
|
message: "Failed accepted client validation for service token data"
|
||||||
});
|
});
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData.findById(
|
const serviceTokenData = await ServiceTokenData.findById(req.authData.authPayload._id)
|
||||||
req.authData.authPayload._id
|
|
||||||
)
|
|
||||||
.select("+encryptedKey +iv +tag")
|
.select("+encryptedKey +iv +tag")
|
||||||
.populate("user").lean();
|
.populate("user")
|
||||||
|
.lean();
|
||||||
|
|
||||||
return res.status(200).json(serviceTokenData);
|
return res.status(200).json(serviceTokenData);
|
||||||
};
|
};
|
||||||
@@ -68,29 +62,7 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||||
let serviceTokenData;
|
let serviceTokenData;
|
||||||
|
|
||||||
const {
|
const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body;
|
||||||
name,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
expiresIn,
|
|
||||||
secretPath,
|
|
||||||
permissions,
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (folders) {
|
|
||||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
|
||||||
if (folder == undefined) {
|
|
||||||
throw BadRequestError({ message: "Path for service token does not exist" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = crypto.randomBytes(16).toString("hex");
|
const secret = crypto.randomBytes(16).toString("hex");
|
||||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||||
@@ -103,10 +75,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
let user, serviceAccount;
|
let user, serviceAccount;
|
||||||
|
|
||||||
if (
|
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||||
req.authData.authMode === AUTH_MODE_JWT &&
|
|
||||||
req.authData.authPayload instanceof User
|
|
||||||
) {
|
|
||||||
user = req.authData.authPayload._id;
|
user = req.authData.authPayload._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,17 +89,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
serviceTokenData = await new ServiceTokenData({
|
serviceTokenData = await new ServiceTokenData({
|
||||||
name,
|
name,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
|
||||||
user,
|
user,
|
||||||
serviceAccount,
|
serviceAccount,
|
||||||
|
scopes,
|
||||||
lastUsed: new Date(),
|
lastUsed: new Date(),
|
||||||
expiresAt,
|
expiresAt,
|
||||||
secretHash,
|
secretHash,
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
secretPath,
|
permissions
|
||||||
permissions,
|
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
// return service token data without sensitive data
|
// return service token data without sensitive data
|
||||||
@@ -142,7 +110,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceToken,
|
serviceToken,
|
||||||
serviceTokenData,
|
serviceTokenData
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,11 +123,9 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||||
const { serviceTokenDataId } = req.params;
|
const { serviceTokenDataId } = req.params;
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(
|
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||||
serviceTokenDataId
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData,
|
serviceTokenData
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const completeAccountSignup = async (req: Request, res: Response) => {
|
export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||||
let user, token, refreshToken;
|
let user;
|
||||||
const {
|
const {
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
@@ -119,7 +119,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
userAgent: req.headers["user-agent"] ?? "",
|
userAgent: req.headers["user-agent"] ?? "",
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
const token = tokens.token;
|
||||||
|
|
||||||
// sending a welcome email to new users
|
// sending a welcome email to new users
|
||||||
if (await getLoopsApiKey()) {
|
if (await getLoopsApiKey()) {
|
||||||
@@ -159,7 +159,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const completeAccountInvite = async (req: Request, res: Response) => {
|
export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||||
let user, token, refreshToken;
|
let user;
|
||||||
const {
|
const {
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
@@ -244,7 +244,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
|||||||
userAgent: req.headers["user-agent"] ?? "",
|
userAgent: req.headers["user-agent"] ?? "",
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
const token = tokens.token;
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie("jid", tokens.refreshToken, {
|
res.cookie("jid", tokens.refreshToken, {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { Types } from "mongoose";
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import {
|
import {
|
||||||
MembershipOrg,
|
|
||||||
User,
|
|
||||||
APIKeyData,
|
APIKeyData,
|
||||||
TokenVersion
|
AuthProvider,
|
||||||
|
MembershipOrg,
|
||||||
|
TokenVersion,
|
||||||
|
User
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { getSaltRounds } from "../../config";
|
import { getSaltRounds } from "../../config";
|
||||||
|
|
||||||
@@ -80,6 +81,67 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update name of the current user to [firstName, lastName].
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateName = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
firstName,
|
||||||
|
lastName
|
||||||
|
}: {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const user = await User.findByIdAndUpdate(
|
||||||
|
req.user._id.toString(),
|
||||||
|
{
|
||||||
|
firstName,
|
||||||
|
lastName: lastName ?? ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update auth provider of the current user to [authProvider]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateAuthProvider = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
authProvider
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
if (req.user?.authProvider === AuthProvider.OKTA_SAML) return res.status(400).send({
|
||||||
|
message: "Failed to update user authentication method because SAML SSO is enforced"
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await User.findByIdAndUpdate(
|
||||||
|
req.user._id.toString(),
|
||||||
|
{
|
||||||
|
authProvider
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
user
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return organizations that the current user is part of.
|
* Return organizations that the current user is part of.
|
||||||
* @param req
|
* @param req
|
||||||
|
|||||||
@@ -1,34 +1,29 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
|
import { Key, Membership, ServiceTokenData, Workspace } from "../../models";
|
||||||
import {
|
import {
|
||||||
Key,
|
pullSecrets as pull,
|
||||||
Membership,
|
v2PushSecrets as push,
|
||||||
ServiceTokenData,
|
reformatPullSecrets
|
||||||
Workspace,
|
|
||||||
} from "../../models";
|
|
||||||
import {
|
|
||||||
pullSecrets as pull,
|
|
||||||
v2PushSecrets as push,
|
|
||||||
reformatPullSecrets,
|
|
||||||
} from "../../helpers/secret";
|
} from "../../helpers/secret";
|
||||||
import { pushKeys } from "../../helpers/key";
|
import { pushKeys } from "../../helpers/key";
|
||||||
import { EventService, TelemetryService } from "../../services";
|
import { EventService, TelemetryService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventPushSecrets } from "../../events";
|
||||||
|
|
||||||
interface V2PushSecret {
|
interface V2PushSecret {
|
||||||
type: string; // personal or shared
|
type: string; // personal or shared
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
secretKeyIV: string;
|
secretKeyIV: string;
|
||||||
secretKeyTag: string;
|
secretKeyTag: string;
|
||||||
secretKeyHash: string;
|
secretKeyHash: string;
|
||||||
secretValueCiphertext: string;
|
secretValueCiphertext: string;
|
||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
secretValueHash: string;
|
secretValueHash: string;
|
||||||
secretCommentCiphertext?: string;
|
secretCommentCiphertext?: string;
|
||||||
secretCommentIV?: string;
|
secretCommentIV?: string;
|
||||||
secretCommentTag?: string;
|
secretCommentTag?: string;
|
||||||
secretCommentHash?: string;
|
secretCommentHash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +34,7 @@ interface V2PushSecret {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
const { keys, environment, channel } = req.body;
|
const { keys, environment, channel } = req.body;
|
||||||
@@ -62,13 +57,13 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
secrets,
|
secrets,
|
||||||
channel: channel ? channel : "cli",
|
channel: channel ? channel : "cli",
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
});
|
});
|
||||||
|
|
||||||
await pushKeys({
|
await pushKeys({
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
keys,
|
keys
|
||||||
});
|
});
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
@@ -79,8 +74,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel ? channel : "cli",
|
channel: channel ? channel : "cli"
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,12 +84,13 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath: "/"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully uploaded workspace secrets",
|
message: "Successfully uploaded workspace secrets"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +101,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const pullSecrets = async (req: Request, res: Response) => {
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
let secrets;
|
let secrets;
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
@@ -128,7 +124,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
channel: channel ? channel : "cli",
|
channel: channel ? channel : "cli",
|
||||||
ipAddress: req.realIP,
|
ipAddress: req.realIP
|
||||||
});
|
});
|
||||||
|
|
||||||
if (channel !== "cli") {
|
if (channel !== "cli") {
|
||||||
@@ -144,18 +140,18 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel ? channel : "cli",
|
channel: channel ? channel : "cli"
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets,
|
secrets
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWorkspaceKey = async (req: Request, res: Response) => {
|
export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Return encrypted project key'
|
#swagger.summary = 'Return encrypted project key'
|
||||||
#swagger.description = 'Return encrypted project key'
|
#swagger.description = 'Return encrypted project key'
|
||||||
|
|
||||||
@@ -183,43 +179,37 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let key;
|
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
key = await Key.findOne({
|
const key = await Key.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
receiver: req.user._id,
|
receiver: req.user._id
|
||||||
}).populate("sender", "+publicKey");
|
}).populate("sender", "+publicKey");
|
||||||
|
|
||||||
if (!key) throw new Error("Failed to find workspace key");
|
if (!key) throw new Error("Failed to find workspace key");
|
||||||
|
|
||||||
return res.status(200).json(key);
|
return res.status(200).json(key);
|
||||||
}
|
};
|
||||||
export const getWorkspaceServiceTokenData = async (
|
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => {
|
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData
|
const serviceTokenData = await ServiceTokenData.find({
|
||||||
.find({
|
workspace: workspaceId
|
||||||
workspace: workspaceId,
|
}).select("+encryptedKey +iv +tag");
|
||||||
})
|
|
||||||
.select("+encryptedKey +iv +tag");
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData,
|
serviceTokenData
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return memberships for workspace with id [workspaceId]
|
* Return memberships for workspace with id [workspaceId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Return project memberships'
|
#swagger.summary = 'Return project memberships'
|
||||||
#swagger.description = 'Return project memberships'
|
#swagger.description = 'Return project memberships'
|
||||||
|
|
||||||
@@ -255,22 +245,22 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
|||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const memberships = await Membership.find({
|
const memberships = await Membership.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).populate("user", "+publicKey");
|
}).populate("user", "+publicKey");
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
memberships,
|
memberships
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update role of membership with id [membershipId] to role [role]
|
* Update role of membership with id [membershipId] to role [role]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Update project membership'
|
#swagger.summary = 'Update project membership'
|
||||||
#swagger.description = 'Update project membership'
|
#swagger.description = 'Update project membership'
|
||||||
|
|
||||||
@@ -323,33 +313,32 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const { membershipId } = req.params;
|
||||||
membershipId,
|
|
||||||
} = req.params;
|
|
||||||
const { role } = req.body;
|
const { role } = req.body;
|
||||||
|
|
||||||
const membership = await Membership.findByIdAndUpdate(
|
const membership = await Membership.findByIdAndUpdate(
|
||||||
membershipId,
|
membershipId,
|
||||||
{
|
{
|
||||||
role,
|
role
|
||||||
}, {
|
},
|
||||||
new: true,
|
{
|
||||||
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership,
|
membership
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete workspace membership with id [membershipId]
|
* Delete workspace membership with id [membershipId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Delete project membership'
|
#swagger.summary = 'Delete project membership'
|
||||||
#swagger.description = 'Delete project membership'
|
#swagger.description = 'Delete project membership'
|
||||||
|
|
||||||
@@ -385,23 +374,21 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const { membershipId } = req.params;
|
||||||
membershipId,
|
|
||||||
} = req.params;
|
|
||||||
|
|
||||||
const membership = await Membership.findByIdAndDelete(membershipId);
|
const membership = await Membership.findByIdAndDelete(membershipId);
|
||||||
|
|
||||||
if (!membership) throw new Error("Failed to delete workspace membership");
|
if (!membership) throw new Error("Failed to delete workspace membership");
|
||||||
|
|
||||||
await Key.deleteMany({
|
await Key.deleteMany({
|
||||||
receiver: membership.user,
|
receiver: membership.user,
|
||||||
workspace: membership.workspace,
|
workspace: membership.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership,
|
membership
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change autoCapitilzation Rule of workspace
|
* Change autoCapitilzation Rule of workspace
|
||||||
@@ -415,18 +402,18 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const workspace = await Workspace.findOneAndUpdate(
|
const workspace = await Workspace.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: workspaceId,
|
_id: workspaceId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
autoCapitalization,
|
autoCapitalization
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully changed autoCapitalization setting",
|
message: "Successfully changed autoCapitalization setting",
|
||||||
workspace,
|
workspace
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const login1 = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
if (!user) throw new Error("Failed to find user");
|
if (!user) throw new Error("Failed to find user");
|
||||||
|
|
||||||
if (user.authProvider) {
|
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
|
||||||
await validateProviderAuthToken({
|
await validateProviderAuthToken({
|
||||||
email,
|
email,
|
||||||
user,
|
user,
|
||||||
@@ -117,7 +117,7 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
if (!user) throw new Error("Failed to find user");
|
if (!user) throw new Error("Failed to find user");
|
||||||
|
|
||||||
if (user.authProvider) {
|
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
|
||||||
await validateProviderAuthToken({
|
await validateProviderAuthToken({
|
||||||
email,
|
email,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -3,8 +3,15 @@ import { Types } from "mongoose";
|
|||||||
import { EventService, SecretService } from "../../services";
|
import { EventService, SecretService } from "../../services";
|
||||||
import { eventPushSecrets } from "../../events";
|
import { eventPushSecrets } from "../../events";
|
||||||
import { BotService } from "../../services";
|
import { BotService } from "../../services";
|
||||||
import { repackageSecretToRaw } from "../../helpers/secrets";
|
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
|
||||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||||
|
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||||
|
import Folder from "../../models/folder";
|
||||||
|
import { getFolderByPath } from "../../services/FolderService";
|
||||||
|
import { BadRequestError } from "../../utils/errors";
|
||||||
|
import { IServiceTokenData } from "../../models";
|
||||||
|
import { requireWorkspaceAuth } from "../../middleware";
|
||||||
|
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secrets for workspace with id [workspaceId] and environment
|
* Return secrets for workspace with id [workspaceId] and environment
|
||||||
@@ -13,30 +20,79 @@ import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getSecretsRaw = async (req: Request, res: Response) => {
|
export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||||
const workspaceId = req.query.workspaceId as string;
|
let workspaceId = req.query.workspaceId as string;
|
||||||
const environment = req.query.environment as string;
|
let environment = req.query.environment as string;
|
||||||
const secretPath = req.query.secretPath as string;
|
let secretPath = req.query.secretPath as string;
|
||||||
|
const includeImports = req.query.include_imports as string;
|
||||||
|
|
||||||
|
// if the service token has single scope, it will get all secrets for that scope by default
|
||||||
|
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
|
||||||
|
if (serviceTokenDetails) {
|
||||||
|
if (
|
||||||
|
serviceTokenDetails.scopes.length == 1 &&
|
||||||
|
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
|
||||||
|
) {
|
||||||
|
const scope = serviceTokenDetails.scopes[0];
|
||||||
|
secretPath = scope.secretPath;
|
||||||
|
environment = scope.environment;
|
||||||
|
workspaceId = serviceTokenDetails.workspace.toString();
|
||||||
|
} else {
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "query",
|
||||||
|
locationEnvironment: "query",
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
|
requireBlindIndicesEnabled: true,
|
||||||
|
requireE2EEOff: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const secrets = await SecretService.getSecrets({
|
const secrets = await SecretService.getSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (includeImports === "true") {
|
||||||
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
let folderId = "root";
|
||||||
|
// if folder exist get it and replace folderid with new one
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, secretPath as string);
|
||||||
|
if (!folder) {
|
||||||
|
throw BadRequestError({ message: "Folder not found" });
|
||||||
|
}
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: secrets.map((secret) =>
|
||||||
|
repackageSecretToRaw({
|
||||||
|
secret,
|
||||||
|
key
|
||||||
|
})
|
||||||
|
),
|
||||||
|
imports: importedSecrets.map((el) => ({
|
||||||
|
...el,
|
||||||
|
secrets: el.secrets.map((secret) => repackageSecretToRaw({ secret, key }))
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: secrets.map((secret) => {
|
secrets: secrets.map((secret) => {
|
||||||
const rep = repackageSecretToRaw({
|
const rep = repackageSecretToRaw({
|
||||||
secret,
|
secret,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
return rep;
|
return rep;
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,54 +114,47 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
secretPath,
|
secretPath,
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretToRaw({
|
||||||
secret,
|
secret,
|
||||||
key,
|
key
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create secret with name [secretName] in plaintext
|
* Create secret with name [secretName] in plaintext
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createSecretRaw = async (req: Request, res: Response) => {
|
export const createSecretRaw = async (req: Request, res: Response) => {
|
||||||
const { secretName } = req.params;
|
const { secretName } = req.params;
|
||||||
const {
|
const { workspaceId, environment, type, secretValue, secretComment, secretPath = "/" } = req.body;
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
type,
|
|
||||||
secretValue,
|
|
||||||
secretComment,
|
|
||||||
secretPath = "/",
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: secretName,
|
plaintext: secretName,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: secretValue,
|
plaintext: secretValue,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: secretComment,
|
plaintext: secretComment,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secret = await SecretService.createSecret({
|
const secret = await SecretService.createSecret({
|
||||||
@@ -123,14 +172,15 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||||
secretCommentIV: secretCommentEncrypted.iv,
|
secretCommentIV: secretCommentEncrypted.iv,
|
||||||
secretCommentTag: secretCommentEncrypted.tag,
|
secretCommentTag: secretCommentEncrypted.tag
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretWithoutBlindIndex = secret.toObject();
|
const secretWithoutBlindIndex = secret.toObject();
|
||||||
@@ -139,10 +189,10 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretToRaw({
|
||||||
secret: secretWithoutBlindIndex,
|
secret: secretWithoutBlindIndex,
|
||||||
key,
|
key
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update secret with name [secretName]
|
* Update secret with name [secretName]
|
||||||
@@ -151,21 +201,15 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||||
const { secretName } = req.params;
|
const { secretName } = req.params;
|
||||||
const {
|
const { workspaceId, environment, type, secretValue, secretPath = "/" } = req.body;
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
type,
|
|
||||||
secretValue,
|
|
||||||
secretPath = "/",
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: secretValue,
|
plaintext: secretValue,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secret = await SecretService.updateSecret({
|
const secret = await SecretService.updateSecret({
|
||||||
@@ -177,21 +221,22 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||||
secretValueIV: secretValueEncrypted.iv,
|
secretValueIV: secretValueEncrypted.iv,
|
||||||
secretValueTag: secretValueEncrypted.tag,
|
secretValueTag: secretValueEncrypted.tag,
|
||||||
secretPath,
|
secretPath
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretToRaw({
|
||||||
secret,
|
secret,
|
||||||
key,
|
key
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -202,12 +247,7 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
||||||
const { secretName } = req.params;
|
const { secretName } = req.params;
|
||||||
const {
|
const { workspaceId, environment, type, secretPath = "/" } = req.body;
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
type,
|
|
||||||
secretPath = "/",
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const { secret } = await SecretService.deleteSecret({
|
const { secret } = await SecretService.deleteSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -215,25 +255,26 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
secretPath,
|
secretPath
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: repackageSecretToRaw({
|
secret: repackageSecretToRaw({
|
||||||
secret,
|
secret,
|
||||||
key,
|
key
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -247,16 +288,35 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
const workspaceId = req.query.workspaceId as string;
|
const workspaceId = req.query.workspaceId as string;
|
||||||
const environment = req.query.environment as string;
|
const environment = req.query.environment as string;
|
||||||
const secretPath = req.query.secretPath as string;
|
const secretPath = req.query.secretPath as string;
|
||||||
|
const includeImports = req.query.include_imports as string;
|
||||||
|
|
||||||
const secrets = await SecretService.getSecrets({
|
const secrets = await SecretService.getSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (includeImports === "true") {
|
||||||
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
let folderId = "root";
|
||||||
|
// if folder exist get it and replace folderid with new one
|
||||||
|
if (folders) {
|
||||||
|
const folder = getFolderByPath(folders.nodes, secretPath as string);
|
||||||
|
if (!folder) {
|
||||||
|
throw BadRequestError({ message: "Folder not found" });
|
||||||
|
}
|
||||||
|
folderId = folder.id;
|
||||||
|
}
|
||||||
|
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets,
|
||||||
|
imports: importedSecrets
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets,
|
secrets
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -278,11 +338,11 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
secretPath,
|
secretPath,
|
||||||
authData: req.authData,
|
authData: req.authData
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret,
|
secret
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,7 +366,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
secretPath = "/",
|
secretPath = "/"
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
const secret = await SecretService.createSecret({
|
const secret = await SecretService.createSecret({
|
||||||
@@ -324,25 +384,25 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretWithoutBlindIndex = secret.toObject();
|
const secretWithoutBlindIndex = secret.toObject();
|
||||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret: secretWithoutBlindIndex,
|
secret: secretWithoutBlindIndex
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update secret with name [secretName]
|
* Update secret with name [secretName]
|
||||||
* @param req
|
* @param req
|
||||||
@@ -357,7 +417,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretPath = "/",
|
secretPath = "/"
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
const secret = await SecretService.updateSecret({
|
const secret = await SecretService.updateSecret({
|
||||||
@@ -369,18 +429,19 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretPath,
|
secretPath
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret,
|
secret
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -391,12 +452,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const deleteSecretByName = async (req: Request, res: Response) => {
|
export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||||
const { secretName } = req.params;
|
const { secretName } = req.params;
|
||||||
const {
|
const { workspaceId, environment, type, secretPath = "/" } = req.body;
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
type,
|
|
||||||
secretPath = "/",
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const { secret } = await SecretService.deleteSecret({
|
const { secret } = await SecretService.deleteSecret({
|
||||||
secretName,
|
secretName,
|
||||||
@@ -404,17 +460,18 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
secretPath,
|
secretPath
|
||||||
});
|
});
|
||||||
|
|
||||||
await EventService.handleEvent({
|
await EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
}),
|
secretPath
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret,
|
secret
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { standardRequest } from "../../config/request";
|
|||||||
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
|
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
|
||||||
import { BadRequestError } from "../../utils/errors";
|
import { BadRequestError } from "../../utils/errors";
|
||||||
import { TelemetryService } from "../../services";
|
import { TelemetryService } from "../../services";
|
||||||
|
import { AuthProvider } from "../../models";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete setting up user by adding their personal and auth information as part of the
|
* Complete setting up user by adding their personal and auth information as part of the
|
||||||
@@ -116,11 +117,13 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
if (!user)
|
if (!user)
|
||||||
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
|
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
|
||||||
|
|
||||||
// initialize default organization and workspace
|
if (user.authProvider !== AuthProvider.OKTA_SAML) {
|
||||||
await initializeDefaultOrg({
|
// initialize default organization and workspace
|
||||||
organizationName,
|
await initializeDefaultOrg({
|
||||||
user,
|
organizationName,
|
||||||
});
|
user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// update organization membership statuses that are
|
// update organization membership statuses that are
|
||||||
// invited to completed with user attached
|
// invited to completed with user attached
|
||||||
@@ -174,7 +177,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
distinctId: email,
|
distinctId: email,
|
||||||
properties: {
|
properties: {
|
||||||
email,
|
email,
|
||||||
attributionSource,
|
...(attributionSource ? { attributionSource } : {})
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as secretController from "./secretController";
|
import * as secretController from "./secretController";
|
||||||
import * as secretSnapshotController from "./secretSnapshotController";
|
import * as secretSnapshotController from "./secretSnapshotController";
|
||||||
import * as organizationsController from "./organizationsController";
|
import * as organizationsController from "./organizationsController";
|
||||||
|
import * as ssoController from "./ssoController";
|
||||||
|
import * as usersController from "./usersController";
|
||||||
import * as workspaceController from "./workspaceController";
|
import * as workspaceController from "./workspaceController";
|
||||||
import * as actionController from "./actionController";
|
import * as actionController from "./actionController";
|
||||||
import * as membershipController from "./membershipController";
|
import * as membershipController from "./membershipController";
|
||||||
@@ -10,6 +12,8 @@ export {
|
|||||||
secretController,
|
secretController,
|
||||||
secretSnapshotController,
|
secretSnapshotController,
|
||||||
organizationsController,
|
organizationsController,
|
||||||
|
ssoController,
|
||||||
|
usersController,
|
||||||
workspaceController,
|
workspaceController,
|
||||||
actionController,
|
actionController,
|
||||||
membershipController,
|
membershipController,
|
||||||
|
|||||||
@@ -27,6 +27,30 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return checkout url for pro trial
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const startOrganizationTrial = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params;
|
||||||
|
const { success_url } = req.body;
|
||||||
|
|
||||||
|
const { data: { url } } = await licenseServerKeyRequest.post(
|
||||||
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/session/trial`,
|
||||||
|
{
|
||||||
|
success_url
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
EELicenseService.delPlan(organizationId);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
url
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the organization's current plan's billing info
|
* Return the organization's current plan's billing info
|
||||||
* @param req
|
* @param req
|
||||||
@@ -154,6 +178,12 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send(data);
|
return res.status(200).send(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete tax id with id [taxId] from organization tax ids on file
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
||||||
const { taxId } = req.params;
|
const { taxId } = req.params;
|
||||||
|
|
||||||
@@ -164,6 +194,12 @@ export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send(data);
|
return res.status(200).send(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return organization's invoices on file
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
||||||
const { data: { invoices } } = await licenseServerKeyRequest.get(
|
const { data: { invoices } } = await licenseServerKeyRequest.get(
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices`
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices`
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { BotOrgService } from "../../../services";
|
||||||
|
import { SSOConfig } from "../../models";
|
||||||
|
import {
|
||||||
|
MembershipOrg,
|
||||||
|
User
|
||||||
|
} from "../../../models";
|
||||||
|
import { getSSOConfigHelper } from "../../helpers/organizations";
|
||||||
|
import { client } from "../../../config";
|
||||||
|
import { ResourceNotFoundError } from "../../../utils/errors";
|
||||||
|
import { getSiteURL } from "../../../config";
|
||||||
|
import { EELicenseService } from "../../services";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect user to appropriate SSO endpoint after successful authentication
|
||||||
|
* to finish inputting their master key for logging in or signing up
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const redirectSSO = async (req: Request, res: Response) => {
|
||||||
|
if (req.isUserCompleted) {
|
||||||
|
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return organization SAML SSO configuration
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getSSOConfig = async (req: Request, res: Response) => {
|
||||||
|
const organizationId = req.query.organizationId as string;
|
||||||
|
|
||||||
|
const data = await getSSOConfigHelper({
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update organization SAML SSO configuration
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateSSOConfig = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
organizationId,
|
||||||
|
authProvider,
|
||||||
|
isActive,
|
||||||
|
entryPoint,
|
||||||
|
issuer,
|
||||||
|
cert,
|
||||||
|
audience
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(organizationId);
|
||||||
|
|
||||||
|
if (!plan.samlSSO) return res.status(400).send({
|
||||||
|
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
||||||
|
});
|
||||||
|
|
||||||
|
interface PatchUpdate {
|
||||||
|
authProvider?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
encryptedEntryPoint?: string;
|
||||||
|
entryPointIV?: string;
|
||||||
|
entryPointTag?: string;
|
||||||
|
encryptedIssuer?: string;
|
||||||
|
issuerIV?: string;
|
||||||
|
issuerTag?: string;
|
||||||
|
encryptedCert?: string;
|
||||||
|
certIV?: string;
|
||||||
|
certTag?: string;
|
||||||
|
encryptedAudience?: string;
|
||||||
|
audienceIV?: string;
|
||||||
|
audienceTag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const update: PatchUpdate = {};
|
||||||
|
|
||||||
|
if (authProvider) {
|
||||||
|
update.authProvider = authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActive !== undefined) {
|
||||||
|
update.isActive = isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = await BotOrgService.getSymmetricKey(
|
||||||
|
new Types.ObjectId(organizationId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entryPoint) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedEntryPoint,
|
||||||
|
iv: entryPointIV,
|
||||||
|
tag: entryPointTag
|
||||||
|
} = client.encryptSymmetric(entryPoint, key);
|
||||||
|
|
||||||
|
update.encryptedEntryPoint = encryptedEntryPoint;
|
||||||
|
update.entryPointIV = entryPointIV;
|
||||||
|
update.entryPointTag = entryPointTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issuer) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedIssuer,
|
||||||
|
iv: issuerIV,
|
||||||
|
tag: issuerTag
|
||||||
|
} = client.encryptSymmetric(issuer, key);
|
||||||
|
|
||||||
|
update.encryptedIssuer = encryptedIssuer;
|
||||||
|
update.issuerIV = issuerIV;
|
||||||
|
update.issuerTag = issuerTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cert) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedCert,
|
||||||
|
iv: certIV,
|
||||||
|
tag: certTag
|
||||||
|
} = client.encryptSymmetric(cert, key);
|
||||||
|
|
||||||
|
update.encryptedCert = encryptedCert;
|
||||||
|
update.certIV = certIV;
|
||||||
|
update.certTag = certTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audience) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedAudience,
|
||||||
|
iv: audienceIV,
|
||||||
|
tag: audienceTag
|
||||||
|
} = client.encryptSymmetric(audience, key);
|
||||||
|
|
||||||
|
update.encryptedAudience = encryptedAudience;
|
||||||
|
update.audienceIV = audienceIV;
|
||||||
|
update.audienceTag = audienceTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssoConfig = await SSOConfig.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
},
|
||||||
|
update,
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ssoConfig) throw ResourceNotFoundError({
|
||||||
|
message: "Failed to find SSO config to update"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (update.isActive !== undefined) {
|
||||||
|
const membershipOrgs = await MembershipOrg.find({
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
}).select("user");
|
||||||
|
|
||||||
|
if (update.isActive) {
|
||||||
|
await User.updateMany(
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authProvider: ssoConfig.authProvider
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await User.updateMany(
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$unset: {
|
||||||
|
authProvider: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(ssoConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create organization SAML SSO configuration
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createSSOConfig = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
organizationId,
|
||||||
|
authProvider,
|
||||||
|
isActive,
|
||||||
|
entryPoint,
|
||||||
|
issuer,
|
||||||
|
cert,
|
||||||
|
audience
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(organizationId);
|
||||||
|
|
||||||
|
if (!plan.samlSSO) return res.status(400).send({
|
||||||
|
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = await BotOrgService.getSymmetricKey(
|
||||||
|
new Types.ObjectId(organizationId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedEntryPoint,
|
||||||
|
iv: entryPointIV,
|
||||||
|
tag: entryPointTag
|
||||||
|
} = client.encryptSymmetric(entryPoint, key);
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedIssuer,
|
||||||
|
iv: issuerIV,
|
||||||
|
tag: issuerTag
|
||||||
|
} = client.encryptSymmetric(issuer, key);
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedCert,
|
||||||
|
iv: certIV,
|
||||||
|
tag: certTag
|
||||||
|
} = client.encryptSymmetric(cert, key);
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedAudience,
|
||||||
|
iv: audienceIV,
|
||||||
|
tag: audienceTag
|
||||||
|
} = client.encryptSymmetric(audience, key);
|
||||||
|
|
||||||
|
const ssoConfig = await new SSOConfig({
|
||||||
|
organization: new Types.ObjectId(organizationId),
|
||||||
|
authProvider,
|
||||||
|
isActive,
|
||||||
|
encryptedEntryPoint,
|
||||||
|
entryPointIV,
|
||||||
|
entryPointTag,
|
||||||
|
encryptedIssuer,
|
||||||
|
issuerIV,
|
||||||
|
issuerTag,
|
||||||
|
encryptedCert,
|
||||||
|
certIV,
|
||||||
|
certTag,
|
||||||
|
encryptedAudience,
|
||||||
|
audienceIV,
|
||||||
|
audienceTag
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return res.status(200).send(ssoConfig);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ip address of the current user
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getMyIp = (req: Request, res: Response) => {
|
||||||
|
return res.status(200).send({
|
||||||
|
ip: req.authData.authIP
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,16 +3,20 @@ import { PipelineStage, Types } from "mongoose";
|
|||||||
import { Secret } from "../../../models";
|
import { Secret } from "../../../models";
|
||||||
import {
|
import {
|
||||||
FolderVersion,
|
FolderVersion,
|
||||||
|
IPType,
|
||||||
ISecretVersion,
|
ISecretVersion,
|
||||||
Log,
|
Log,
|
||||||
SecretSnapshot,
|
SecretSnapshot,
|
||||||
SecretVersion,
|
SecretVersion,
|
||||||
TFolderRootVersionSchema,
|
TFolderRootVersionSchema,
|
||||||
|
TrustedIP
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { EESecretService } from "../../services";
|
import { EESecretService } from "../../services";
|
||||||
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
||||||
import Folder, { TFolderSchema } from "../../../models/folder";
|
import Folder, { TFolderSchema } from "../../../models/folder";
|
||||||
import { searchByFolderId } from "../../../services/FolderService";
|
import { searchByFolderId } from "../../../services/FolderService";
|
||||||
|
import { EELicenseService } from "../../services";
|
||||||
|
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secret snapshots for workspace with id [workspaceId]
|
* Return secret snapshots for workspace with id [workspaceId]
|
||||||
@@ -588,3 +592,147 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
|||||||
logs,
|
logs,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return trusted ips for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
const trustedIps = await TrustedIP.find({
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
trustedIps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a trusted ip to workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
const {
|
||||||
|
ipAddress: ip,
|
||||||
|
comment,
|
||||||
|
isActive
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||||
|
|
||||||
|
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||||
|
message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
|
||||||
|
const isValidIPOrCidr = isValidIpOrCidr(ip);
|
||||||
|
|
||||||
|
if (!isValidIPOrCidr) return res.status(400).send({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ipAddress, type, prefix } = extractIPDetails(ip);
|
||||||
|
|
||||||
|
const trustedIp = await new TrustedIP({
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
ipAddress,
|
||||||
|
type,
|
||||||
|
prefix,
|
||||||
|
isActive,
|
||||||
|
comment,
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
trustedIp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update trusted ip with id [trustedIpId] workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId, trustedIpId } = req.params;
|
||||||
|
const {
|
||||||
|
ipAddress: ip,
|
||||||
|
comment
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||||
|
|
||||||
|
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||||
|
message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range."
|
||||||
|
});
|
||||||
|
|
||||||
|
const isValidIPOrCidr = isValidIpOrCidr(ip);
|
||||||
|
|
||||||
|
if (!isValidIPOrCidr) return res.status(400).send({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ipAddress, type, prefix } = extractIPDetails(ip);
|
||||||
|
|
||||||
|
const updateObject: {
|
||||||
|
ipAddress: string;
|
||||||
|
type: IPType;
|
||||||
|
comment: string;
|
||||||
|
prefix?: number;
|
||||||
|
$unset?: {
|
||||||
|
prefix: number;
|
||||||
|
}
|
||||||
|
} = {
|
||||||
|
ipAddress,
|
||||||
|
type,
|
||||||
|
comment
|
||||||
|
};
|
||||||
|
|
||||||
|
if (prefix !== undefined) {
|
||||||
|
updateObject.prefix = prefix;
|
||||||
|
} else {
|
||||||
|
updateObject.$unset = { prefix: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const trustedIp = await TrustedIP.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: new Types.ObjectId(trustedIpId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
},
|
||||||
|
updateObject,
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
trustedIp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete IP access range from workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||||
|
const { workspaceId, trustedIpId } = req.params;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||||
|
|
||||||
|
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||||
|
message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range."
|
||||||
|
});
|
||||||
|
|
||||||
|
const trustedIp = await TrustedIP.findOneAndDelete({
|
||||||
|
_id: new Types.ObjectId(trustedIpId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
trustedIp
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import {
|
||||||
|
SSOConfig
|
||||||
|
} from "../models";
|
||||||
|
import {
|
||||||
|
BotOrgService
|
||||||
|
} from "../../services";
|
||||||
|
import { client } from "../../config";
|
||||||
|
import { ValidationError } from "../../utils/errors";
|
||||||
|
|
||||||
|
export const getSSOConfigHelper = async ({
|
||||||
|
organizationId,
|
||||||
|
ssoConfigId
|
||||||
|
}: {
|
||||||
|
organizationId?: Types.ObjectId;
|
||||||
|
ssoConfigId?: Types.ObjectId;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
if (!organizationId && !ssoConfigId) throw ValidationError({
|
||||||
|
message: "Getting SSO data requires either id of organization or SSO data"
|
||||||
|
});
|
||||||
|
|
||||||
|
const ssoConfig = await SSOConfig.findOne({
|
||||||
|
...(organizationId ? { organization: organizationId } : {}),
|
||||||
|
...(ssoConfigId ? { _id: ssoConfigId } : {})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ssoConfig) throw new Error("Failed to find organization SSO data");
|
||||||
|
|
||||||
|
const key = await BotOrgService.getSymmetricKey(
|
||||||
|
ssoConfig.organization
|
||||||
|
);
|
||||||
|
|
||||||
|
const entryPoint = client.decryptSymmetric(
|
||||||
|
ssoConfig.encryptedEntryPoint,
|
||||||
|
key,
|
||||||
|
ssoConfig.entryPointIV,
|
||||||
|
ssoConfig.entryPointTag
|
||||||
|
);
|
||||||
|
|
||||||
|
const issuer = client.decryptSymmetric(
|
||||||
|
ssoConfig.encryptedIssuer,
|
||||||
|
key,
|
||||||
|
ssoConfig.issuerIV,
|
||||||
|
ssoConfig.issuerTag
|
||||||
|
);
|
||||||
|
|
||||||
|
const cert = client.decryptSymmetric(
|
||||||
|
ssoConfig.encryptedCert,
|
||||||
|
key,
|
||||||
|
ssoConfig.certIV,
|
||||||
|
ssoConfig.certTag
|
||||||
|
);
|
||||||
|
|
||||||
|
const audience = client.decryptSymmetric(
|
||||||
|
ssoConfig.encryptedAudience,
|
||||||
|
key,
|
||||||
|
ssoConfig.audienceIV,
|
||||||
|
ssoConfig.audienceTag
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
_id: ssoConfig._id,
|
||||||
|
organization: ssoConfig.organization,
|
||||||
|
authProvider: ssoConfig.authProvider,
|
||||||
|
isActive: ssoConfig.isActive,
|
||||||
|
entryPoint,
|
||||||
|
issuer,
|
||||||
|
cert,
|
||||||
|
audience
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import requireLicenseAuth from "./requireLicenseAuth";
|
|
||||||
import requireSecretSnapshotAuth from "./requireSecretSnapshotAuth";
|
import requireSecretSnapshotAuth from "./requireSecretSnapshotAuth";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
requireLicenseAuth,
|
|
||||||
requireSecretSnapshotAuth,
|
requireSecretSnapshotAuth,
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate if organization hosting meets license requirements to
|
|
||||||
* access a license-specific route.
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String[]} obj.acceptedTiers
|
|
||||||
*/
|
|
||||||
const requireLicenseAuth = ({
|
|
||||||
acceptedTiers,
|
|
||||||
}: {
|
|
||||||
acceptedTiers: string[];
|
|
||||||
}) => {
|
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
try {
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default requireLicenseAuth;
|
|
||||||
@@ -66,6 +66,4 @@ const actionSchema = new Schema<IAction>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const Action = model<IAction>("Action", actionSchema);
|
export const Action = model<IAction>("Action", actionSchema);
|
||||||
|
|
||||||
export default Action;
|
|
||||||
@@ -52,9 +52,7 @@ const folderRootVersionSchema = new Schema<TFolderRootVersionSchema>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const FolderVersion = model<TFolderRootVersionSchema>(
|
export const FolderVersion = model<TFolderRootVersionSchema>(
|
||||||
"FolderVersion",
|
"FolderVersion",
|
||||||
folderRootVersionSchema
|
folderRootVersionSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
export default FolderVersion;
|
|
||||||
@@ -1,18 +1,7 @@
|
|||||||
import SecretSnapshot, { ISecretSnapshot } from "./secretSnapshot";
|
export * from "./secretSnapshot";
|
||||||
import SecretVersion, { ISecretVersion } from "./secretVersion";
|
export * from "./secretVersion";
|
||||||
import FolderVersion, { TFolderRootVersionSchema } from "./folderVersion";
|
export * from "./folderVersion";
|
||||||
import Log, { ILog } from "./log";
|
export * from "./log";
|
||||||
import Action, { IAction } from "./action";
|
export * from "./action";
|
||||||
|
export * from "./ssoConfig";
|
||||||
export {
|
export * from "./trustedIp";
|
||||||
SecretSnapshot,
|
|
||||||
ISecretSnapshot,
|
|
||||||
SecretVersion,
|
|
||||||
ISecretVersion,
|
|
||||||
FolderVersion,
|
|
||||||
TFolderRootVersionSchema,
|
|
||||||
Log,
|
|
||||||
ILog,
|
|
||||||
Action,
|
|
||||||
IAction,
|
|
||||||
};
|
|
||||||
@@ -63,11 +63,10 @@ const logSchema = new Schema<ILog>(
|
|||||||
ipAddress: {
|
ipAddress: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
timestamps: true,
|
{
|
||||||
}
|
timestamps: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const Log = model<ILog>("Log", logSchema);
|
export const Log = model<ILog>("Log", logSchema);
|
||||||
|
|
||||||
export default Log;
|
|
||||||
@@ -46,9 +46,7 @@ const secretSnapshotSchema = new Schema<ISecretSnapshot>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const SecretSnapshot = model<ISecretSnapshot>(
|
export const SecretSnapshot = model<ISecretSnapshot>(
|
||||||
"SecretSnapshot",
|
"SecretSnapshot",
|
||||||
secretSnapshotSchema
|
secretSnapshotSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
export default SecretSnapshot;
|
|
||||||
@@ -124,9 +124,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const SecretVersion = model<ISecretVersion>(
|
export const SecretVersion = model<ISecretVersion>(
|
||||||
"SecretVersion",
|
"SecretVersion",
|
||||||
secretVersionSchema
|
secretVersionSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
export default SecretVersion;
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface ISSOConfig {
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
authProvider: "okta-saml"
|
||||||
|
isActive: boolean;
|
||||||
|
encryptedEntryPoint: string;
|
||||||
|
entryPointIV: string;
|
||||||
|
entryPointTag: string;
|
||||||
|
encryptedIssuer: string;
|
||||||
|
issuerIV: string;
|
||||||
|
issuerTag: string;
|
||||||
|
encryptedCert: string;
|
||||||
|
certIV: string;
|
||||||
|
certTag: string;
|
||||||
|
encryptedAudience: string;
|
||||||
|
audienceIV: string;
|
||||||
|
audienceTag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssoConfigSchema = new Schema<ISSOConfig>(
|
||||||
|
{
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization"
|
||||||
|
},
|
||||||
|
authProvider: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
"okta-saml"
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
encryptedEntryPoint: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
entryPointIV: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
entryPointTag: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
encryptedIssuer: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
issuerIV: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
issuerTag: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
encryptedCert: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
certIV: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
certTag: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
encryptedAudience: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
audienceIV: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
audienceTag: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SSOConfig = model<ISSOConfig>("SSOConfig", ssoConfigSchema);
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export enum IPType {
|
||||||
|
IPV4 = "ipv4",
|
||||||
|
IPV6 = "ipv6"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITrustedIP {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
ipAddress: string;
|
||||||
|
type: "ipv4" | "ipv6", // either IPv4/IPv6 address or network IPv4/IPv6 address
|
||||||
|
isActive: boolean;
|
||||||
|
comment: string;
|
||||||
|
prefix?: number; // CIDR
|
||||||
|
}
|
||||||
|
|
||||||
|
const trustedIpSchema = new Schema<ITrustedIP>(
|
||||||
|
{
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
ipAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
IPType.IPV4,
|
||||||
|
IPType.IPV6
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
prefix: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TrustedIP = model<ITrustedIP>("TrustedIP", trustedIpSchema);
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import secret from "./secret";
|
import secret from "./secret";
|
||||||
import secretSnapshot from "./secretSnapshot";
|
import secretSnapshot from "./secretSnapshot";
|
||||||
import organizations from "./organizations";
|
import organizations from "./organizations";
|
||||||
|
import sso from "./sso";
|
||||||
|
import users from "./users";
|
||||||
import workspace from "./workspace";
|
import workspace from "./workspace";
|
||||||
import action from "./action";
|
import action from "./action";
|
||||||
import cloudProducts from "./cloudProducts";
|
import cloudProducts from "./cloudProducts";
|
||||||
@@ -9,6 +11,8 @@ export {
|
|||||||
secret,
|
secret,
|
||||||
secretSnapshot,
|
secretSnapshot,
|
||||||
organizations,
|
organizations,
|
||||||
|
sso,
|
||||||
|
users,
|
||||||
workspace,
|
workspace,
|
||||||
action,
|
action,
|
||||||
cloudProducts,
|
cloudProducts,
|
||||||
|
|||||||
@@ -41,6 +41,21 @@ router.get(
|
|||||||
organizationsController.getOrganizationPlan
|
organizationsController.getOrganizationPlan
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:organizationId/session/trial",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
body("success_url").exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
organizationsController.startOrganizationTrial
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/plan/billing",
|
"/:organizationId/plan/billing",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import passport from "passport";
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireOrganizationAuth,
|
||||||
|
validateRequest,
|
||||||
|
} from "../../../middleware";
|
||||||
|
import { body, query } from "express-validator";
|
||||||
|
import { ssoController } from "../../controllers/v1";
|
||||||
|
import { authLimiter } from "../../../helpers/rateLimiter";
|
||||||
|
import {
|
||||||
|
ACCEPTED,
|
||||||
|
ADMIN,
|
||||||
|
OWNER
|
||||||
|
} from "../../../variables";
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/redirect/google",
|
||||||
|
authLimiter,
|
||||||
|
(req, res, next) => {
|
||||||
|
passport.authenticate("google", {
|
||||||
|
scope: ["profile", "email"],
|
||||||
|
session: false,
|
||||||
|
...(req.query.callback_port ? {
|
||||||
|
state: req.query.callback_port as string
|
||||||
|
} : {})
|
||||||
|
})(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/google",
|
||||||
|
passport.authenticate("google", {
|
||||||
|
failureRedirect: "/login/provider/error",
|
||||||
|
session: false
|
||||||
|
}),
|
||||||
|
ssoController.redirectSSO
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/redirect/saml2/:ssoIdentifier",
|
||||||
|
authLimiter,
|
||||||
|
(req, res, next) => {
|
||||||
|
const options = {
|
||||||
|
failureRedirect: "/",
|
||||||
|
additionalParams: {
|
||||||
|
RelayState: req.query.callback_port ?? ""
|
||||||
|
},
|
||||||
|
};
|
||||||
|
passport.authenticate("saml", options)(req, res, next);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post("/saml2/:ssoIdentifier",
|
||||||
|
passport.authenticate("saml", {
|
||||||
|
failureRedirect: "/login/provider/error",
|
||||||
|
failureFlash: true,
|
||||||
|
session: false
|
||||||
|
}),
|
||||||
|
ssoController.redirectSSO
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/config",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
locationOrganizationId: "query"
|
||||||
|
}),
|
||||||
|
query("organizationId").exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
ssoController.getSSOConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/config",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
locationOrganizationId: "body"
|
||||||
|
}),
|
||||||
|
body("organizationId").exists().trim(),
|
||||||
|
body("authProvider").exists().isString(),
|
||||||
|
body("isActive").exists().isBoolean(),
|
||||||
|
body("entryPoint").exists().isString(),
|
||||||
|
body("issuer").exists().isString(),
|
||||||
|
body("cert").exists().isString(),
|
||||||
|
body("audience").exists().isString(),
|
||||||
|
validateRequest,
|
||||||
|
ssoController.createSSOConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/config",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
locationOrganizationId: "body"
|
||||||
|
}),
|
||||||
|
body("organizationId").exists().trim(),
|
||||||
|
body("authProvider").optional().isString(),
|
||||||
|
body("isActive").optional().isBoolean(),
|
||||||
|
body("entryPoint").optional().isString(),
|
||||||
|
body("issuer").optional().isString(),
|
||||||
|
body("cert").optional().isString(),
|
||||||
|
body("audience").optional().isString(),
|
||||||
|
validateRequest,
|
||||||
|
ssoController.updateSSOConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth
|
||||||
|
} from "../../../middleware";
|
||||||
|
import { AUTH_MODE_API_KEY, AUTH_MODE_JWT } from "../../../variables";
|
||||||
|
import { usersController } from "../../controllers/v1";
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/me/ip",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||||
|
}),
|
||||||
|
usersController.getMyIp
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -6,13 +6,18 @@ import {
|
|||||||
validateRequest,
|
validateRequest,
|
||||||
} from "../../../middleware";
|
} from "../../../middleware";
|
||||||
import { body, param, query } from "express-validator";
|
import { body, param, query } from "express-validator";
|
||||||
import { ADMIN, MEMBER } from "../../../variables";
|
import {
|
||||||
|
ADMIN,
|
||||||
|
AUTH_MODE_API_KEY,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
MEMBER
|
||||||
|
} from "../../../variables";
|
||||||
import { workspaceController } from "../../controllers/v1";
|
import { workspaceController } from "../../controllers/v1";
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/secret-snapshots",
|
"/:workspaceId/secret-snapshots",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ["jwt", "apiKey"],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -30,7 +35,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/secret-snapshots/count",
|
"/:workspaceId/secret-snapshots/count",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ["jwt"],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -46,7 +51,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/:workspaceId/secret-snapshots/rollback",
|
"/:workspaceId/secret-snapshots/rollback",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ["jwt", "apiKey"],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -63,7 +68,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/logs",
|
"/:workspaceId/logs",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ["jwt", "apiKey"],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -79,4 +84,66 @@ router.get(
|
|||||||
workspaceController.getWorkspaceLogs
|
workspaceController.getWorkspaceLogs
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:workspaceId/trusted-ips",
|
||||||
|
param("workspaceId").exists().isString().trim(),
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "params",
|
||||||
|
}),
|
||||||
|
workspaceController.getWorkspaceTrustedIps
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:workspaceId/trusted-ips",
|
||||||
|
param("workspaceId").exists().isString().trim(),
|
||||||
|
body("ipAddress").exists().isString().trim(),
|
||||||
|
body("comment").default("").isString().trim(),
|
||||||
|
body("isActive").exists().isBoolean(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
locationWorkspaceId: "params",
|
||||||
|
}),
|
||||||
|
workspaceController.addWorkspaceTrustedIp
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:workspaceId/trusted-ips/:trustedIpId",
|
||||||
|
param("workspaceId").exists().isString().trim(),
|
||||||
|
param("trustedIpId").exists().isString().trim(),
|
||||||
|
body("ipAddress").isString().trim().default(""),
|
||||||
|
body("comment").default("").isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
locationWorkspaceId: "params",
|
||||||
|
}),
|
||||||
|
workspaceController.updateWorkspaceTrustedIp
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:workspaceId/trusted-ips/:trustedIpId",
|
||||||
|
param("workspaceId").exists().isString().trim(),
|
||||||
|
param("trustedIpId").exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
locationWorkspaceId: "params",
|
||||||
|
}),
|
||||||
|
workspaceController.deleteWorkspaceTrustedIp
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -26,12 +26,15 @@ interface FeatureSet {
|
|||||||
environmentsUsed: number;
|
environmentsUsed: number;
|
||||||
secretVersioning: boolean;
|
secretVersioning: boolean;
|
||||||
pitRecovery: boolean;
|
pitRecovery: boolean;
|
||||||
|
ipAllowlisting: boolean;
|
||||||
rbac: boolean;
|
rbac: boolean;
|
||||||
customRateLimits: boolean;
|
customRateLimits: boolean;
|
||||||
customAlerts: boolean;
|
customAlerts: boolean;
|
||||||
auditLogs: boolean;
|
auditLogs: boolean;
|
||||||
status: 'incomplete' | 'incomplete_expired' | 'trialing' | 'active' | 'past_due' | 'canceled' | 'unpaid' | null;
|
samlSSO: boolean;
|
||||||
|
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
||||||
trial_end: number | null;
|
trial_end: number | null;
|
||||||
|
has_used_trial: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,12 +61,15 @@ class EELicenseService {
|
|||||||
environmentsUsed: 0,
|
environmentsUsed: 0,
|
||||||
secretVersioning: true,
|
secretVersioning: true,
|
||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
|
ipAllowlisting: false,
|
||||||
rbac: true,
|
rbac: true,
|
||||||
customRateLimits: true,
|
customRateLimits: true,
|
||||||
customAlerts: true,
|
customAlerts: true,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
|
samlSSO: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null
|
trial_end: null,
|
||||||
|
has_used_trial: true
|
||||||
}
|
}
|
||||||
|
|
||||||
public localFeatureSet: NodeCache;
|
public localFeatureSet: NodeCache;
|
||||||
@@ -71,7 +77,7 @@ class EELicenseService {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this._isLicenseValid = true;
|
this._isLicenseValid = true;
|
||||||
this.localFeatureSet = new NodeCache({
|
this.localFeatureSet = new NodeCache({
|
||||||
stdTTL: 300,
|
stdTTL: 60,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +118,12 @@ class EELicenseService {
|
|||||||
await this.getPlan(organizationId, workspaceId);
|
await this.getPlan(organizationId, workspaceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async delPlan(organizationId: string) {
|
||||||
|
if (this.instanceType === "cloud") {
|
||||||
|
this.localFeatureSet.del(`${organizationId}-`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async initGlobalFeatureSet() {
|
public async initGlobalFeatureSet() {
|
||||||
const licenseServerKey = await getLicenseServerKey();
|
const licenseServerKey = await getLicenseServerKey();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { eventPushSecrets } from "./secret"
|
import { eventPushSecrets } from "./secret";
|
||||||
|
import { eventStartIntegration } from "./integration";
|
||||||
|
|
||||||
export {
|
export { eventPushSecrets, eventStartIntegration };
|
||||||
eventPushSecrets,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import { EVENT_START_INTEGRATION } from "../variables";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return event for starting integrations
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.workspaceId - id of workspace to push secrets to
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const eventStartIntegration = ({
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
}: {
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
}) => {
|
||||||
|
return {
|
||||||
|
name: EVENT_START_INTEGRATION,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
payload: {}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,64 +1,54 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import { EVENT_PULL_SECRETS, EVENT_PUSH_SECRETS } from "../variables";
|
||||||
EVENT_PULL_SECRETS,
|
|
||||||
EVENT_PUSH_SECRETS,
|
|
||||||
} from "../variables";
|
|
||||||
|
|
||||||
interface PushSecret {
|
interface PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
ivKey: string;
|
ivKey: string;
|
||||||
tagKey: string;
|
tagKey: string;
|
||||||
hashKey: string;
|
hashKey: string;
|
||||||
ciphertextValue: string;
|
ciphertextValue: string;
|
||||||
ivValue: string;
|
ivValue: string;
|
||||||
tagValue: string;
|
tagValue: string;
|
||||||
hashValue: string;
|
hashValue: string;
|
||||||
type: "shared" | "personal";
|
type: "shared" | "personal";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return event for pushing secrets
|
* Return event for pushing secrets
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.workspaceId - id of workspace to push secrets to
|
* @param {String} obj.workspaceId - id of workspace to push secrets to
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const eventPushSecrets = ({
|
const eventPushSecrets = ({
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
}: {
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
}) => {
|
||||||
|
return {
|
||||||
|
name: EVENT_PUSH_SECRETS,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
}: {
|
secretPath,
|
||||||
workspaceId: Types.ObjectId;
|
payload: {}
|
||||||
environment?: string;
|
};
|
||||||
}) => {
|
};
|
||||||
return ({
|
|
||||||
name: EVENT_PUSH_SECRETS,
|
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
payload: {
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return event for pulling secrets
|
* Return event for pulling secrets
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.workspaceId - id of workspace to pull secrets from
|
* @param {String} obj.workspaceId - id of workspace to pull secrets from
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const eventPullSecrets = ({
|
const eventPullSecrets = ({ workspaceId }: { workspaceId: string }) => {
|
||||||
|
return {
|
||||||
|
name: EVENT_PULL_SECRETS,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
}: {
|
payload: {}
|
||||||
workspaceId: string;
|
};
|
||||||
}) => {
|
};
|
||||||
return ({
|
|
||||||
name: EVENT_PULL_SECRETS,
|
|
||||||
workspaceId,
|
|
||||||
payload: {
|
|
||||||
|
|
||||||
},
|
export { eventPushSecrets };
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
eventPushSecrets,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
|
import { BotOrg } from "../models";
|
||||||
|
import { decryptSymmetric128BitHexKeyUTF8 } from "../utils/crypto";
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
ENCODING_SCHEME_UTF8
|
||||||
|
} from "../variables";
|
||||||
|
import { InternalServerError } from "../utils/errors";
|
||||||
|
import { encryptSymmetric128BitHexKeyUTF8, generateKeyPair } from "../utils/crypto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a bot with name [name] for organization with id [organizationId]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.name - name of bot
|
||||||
|
* @param {String} obj.organizationId - id of organization that bot belongs to
|
||||||
|
*/
|
||||||
|
export const createBotOrg = async ({
|
||||||
|
name,
|
||||||
|
organizationId,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
organizationId: Types.ObjectId;
|
||||||
|
}) => {
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
|
const { publicKey, privateKey } = generateKeyPair();
|
||||||
|
const key = client.createSymmetricKey();
|
||||||
|
|
||||||
|
if (rootEncryptionKey) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: privateKeyIV,
|
||||||
|
tag: privateKeyTag
|
||||||
|
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSymmetricKey,
|
||||||
|
iv: symmetricKeyIV,
|
||||||
|
tag: symmetricKeyTag
|
||||||
|
} = client.encryptSymmetric(key, rootEncryptionKey);
|
||||||
|
|
||||||
|
return await new BotOrg({
|
||||||
|
name,
|
||||||
|
organization: organizationId,
|
||||||
|
publicKey,
|
||||||
|
encryptedSymmetricKey,
|
||||||
|
symmetricKeyIV,
|
||||||
|
symmetricKeyTag,
|
||||||
|
symmetricKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
symmetricKeyKeyEncoding: ENCODING_SCHEME_BASE64,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
privateKeyIV,
|
||||||
|
privateKeyTag,
|
||||||
|
privateKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
privateKeyKeyEncoding: ENCODING_SCHEME_BASE64
|
||||||
|
}).save();
|
||||||
|
} else if (encryptionKey) {
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: privateKeyIV,
|
||||||
|
tag: privateKeyTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: privateKey,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSymmetricKey,
|
||||||
|
iv: symmetricKeyIV,
|
||||||
|
tag: symmetricKeyTag
|
||||||
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
|
plaintext: key,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return await new BotOrg({
|
||||||
|
name,
|
||||||
|
organization: organizationId,
|
||||||
|
publicKey,
|
||||||
|
encryptedSymmetricKey,
|
||||||
|
symmetricKeyIV,
|
||||||
|
symmetricKeyTag,
|
||||||
|
symmetricKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
symmetricKeyKeyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
privateKeyIV,
|
||||||
|
privateKeyTag,
|
||||||
|
privateKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
privateKeyKeyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}).save();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerError({
|
||||||
|
message: "Failed to create new organization bot due to missing encryption key",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSymmetricKeyHelper = async (organizationId: Types.ObjectId) => {
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
const encryptionKey = await getEncryptionKey();
|
||||||
|
|
||||||
|
const botOrg = await BotOrg.findOne({
|
||||||
|
organization: organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!botOrg) throw new Error("Failed to find organization bot");
|
||||||
|
|
||||||
|
if (rootEncryptionKey && botOrg.symmetricKeyKeyEncoding == ENCODING_SCHEME_BASE64) {
|
||||||
|
const key = client.decryptSymmetric(
|
||||||
|
botOrg.encryptedSymmetricKey,
|
||||||
|
rootEncryptionKey,
|
||||||
|
botOrg.symmetricKeyIV,
|
||||||
|
botOrg.symmetricKeyTag
|
||||||
|
);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
} else if (encryptionKey && botOrg.symmetricKeyKeyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
|
const key = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: botOrg.encryptedSymmetricKey,
|
||||||
|
iv: botOrg.symmetricKeyIV,
|
||||||
|
tag: botOrg.symmetricKeyTag,
|
||||||
|
key: encryptionKey
|
||||||
|
});
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerError({
|
||||||
|
message: "Failed to match encryption key with organization bot symmetric key encoding"
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Bot } from "../models";
|
import { Bot } from "../models";
|
||||||
import { EVENT_PUSH_SECRETS } from "../variables";
|
import { EVENT_PUSH_SECRETS, EVENT_START_INTEGRATION } from "../variables";
|
||||||
import { IntegrationService } from "../services";
|
import { IntegrationService } from "../services";
|
||||||
|
import { triggerWebhook } from "../services/WebhookService";
|
||||||
|
|
||||||
interface Event {
|
interface Event {
|
||||||
name: string;
|
name: string;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
|
secretPath?: string;
|
||||||
payload: any;
|
payload: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,22 +21,31 @@ interface Event {
|
|||||||
* @param {Object} obj.event.payload - payload of event (depends on event)
|
* @param {Object} obj.event.payload - payload of event (depends on event)
|
||||||
*/
|
*/
|
||||||
export const handleEventHelper = async ({ event }: { event: Event }) => {
|
export const handleEventHelper = async ({ event }: { event: Event }) => {
|
||||||
const { workspaceId, environment } = event;
|
const { workspaceId, environment, secretPath } = event;
|
||||||
|
|
||||||
// TODO: moduralize bot check into separate function
|
// TODO: moduralize bot check into separate function
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
isActive: true,
|
isActive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!bot) return;
|
|
||||||
|
|
||||||
switch (event.name) {
|
switch (event.name) {
|
||||||
case EVENT_PUSH_SECRETS:
|
case EVENT_PUSH_SECRETS:
|
||||||
IntegrationService.syncIntegrations({
|
if (bot) {
|
||||||
workspaceId,
|
await IntegrationService.syncIntegrations({
|
||||||
environment,
|
workspaceId,
|
||||||
});
|
environment
|
||||||
|
});
|
||||||
|
}
|
||||||
|
triggerWebhook(workspaceId.toString(), environment || "", secretPath || "");
|
||||||
|
break;
|
||||||
|
case EVENT_START_INTEGRATION:
|
||||||
|
if (bot) {
|
||||||
|
IntegrationService.syncIntegrations({
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { UnauthorizedRequestError } from "../utils/errors";
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
import * as Sentry from "@sentry/node";
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@@ -115,46 +116,52 @@ export const syncIntegrationsHelper = async ({
|
|||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const integrations = await Integration.find({
|
try {
|
||||||
workspace: workspaceId,
|
const integrations = await Integration.find({
|
||||||
...(environment
|
workspace: workspaceId,
|
||||||
? {
|
...(environment
|
||||||
|
? {
|
||||||
environment,
|
environment,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
isActive: true,
|
isActive: true,
|
||||||
app: { $ne: null },
|
app: { $ne: null },
|
||||||
});
|
|
||||||
|
|
||||||
// for each workspace integration, sync/push secrets
|
|
||||||
// to that integration
|
|
||||||
for await (const integration of integrations) {
|
|
||||||
// get workspace, environment (shared) secrets
|
|
||||||
const secrets = await BotService.getSecrets({
|
|
||||||
// issue here?
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
environment: integration.environment,
|
|
||||||
secretPath: integration.secretPath,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findById(
|
// for each workspace integration, sync/push secrets
|
||||||
integration.integrationAuth
|
// to that integration
|
||||||
);
|
for await (const integration of integrations) {
|
||||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
// get workspace, environment (shared) secrets
|
||||||
|
const secrets = await BotService.getSecrets({
|
||||||
|
// issue here?
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
environment: integration.environment,
|
||||||
|
secretPath: integration.secretPath,
|
||||||
|
});
|
||||||
|
|
||||||
// get integration auth access token
|
const integrationAuth = await IntegrationAuth.findById(
|
||||||
const access = await getIntegrationAuthAccessHelper({
|
integration.integrationAuth
|
||||||
integrationAuthId: integration.integrationAuth,
|
);
|
||||||
});
|
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||||
|
|
||||||
// sync secrets to integration
|
// get integration auth access token
|
||||||
await syncSecrets({
|
const access = await getIntegrationAuthAccessHelper({
|
||||||
integration,
|
integrationAuthId: integration.integrationAuth,
|
||||||
integrationAuth,
|
});
|
||||||
secrets,
|
|
||||||
accessId: access.accessId === undefined ? null : access.accessId,
|
// sync secrets to integration
|
||||||
accessToken: access.accessToken,
|
await syncSecrets({
|
||||||
});
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
secrets,
|
||||||
|
accessId: access.accessId === undefined ? null : access.accessId,
|
||||||
|
accessToken: access.accessToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.captureException(err);
|
||||||
|
console.log(`syncIntegrationsHelper: failed with [workspaceId=${workspaceId}] [environment=${environment}]`, err) // eslint-disable-line no-use-before-define
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import {
|
|||||||
licenseKeyRequest,
|
licenseKeyRequest,
|
||||||
licenseServerKeyRequest,
|
licenseServerKeyRequest,
|
||||||
} from "../config/request";
|
} from "../config/request";
|
||||||
|
import {
|
||||||
|
createBotOrg
|
||||||
|
} from "./botOrg";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an organization with name [name]
|
* Create an organization with name [name]
|
||||||
@@ -29,6 +32,7 @@ export const createOrganization = async ({
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const licenseServerKey = await getLicenseServerKey();
|
const licenseServerKey = await getLicenseServerKey();
|
||||||
let organization;
|
let organization;
|
||||||
|
|
||||||
@@ -52,6 +56,12 @@ export const createOrganization = async ({
|
|||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize bot for organization
|
||||||
|
await createBotOrg({
|
||||||
|
name,
|
||||||
|
organizationId: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
return organization;
|
return organization;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+151
-171
@@ -4,13 +4,14 @@ import {
|
|||||||
DeleteSecretParams,
|
DeleteSecretParams,
|
||||||
GetSecretParams,
|
GetSecretParams,
|
||||||
GetSecretsParams,
|
GetSecretsParams,
|
||||||
UpdateSecretParams,
|
UpdateSecretParams
|
||||||
} from "../interfaces/services/SecretService";
|
} from "../interfaces/services/SecretService";
|
||||||
import {
|
import {
|
||||||
ISecret,
|
ISecret,
|
||||||
|
IServiceTokenData,
|
||||||
Secret,
|
Secret,
|
||||||
SecretBlindIndexData,
|
SecretBlindIndexData,
|
||||||
ServiceTokenData,
|
ServiceTokenData
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import { SecretVersion } from "../ee/models";
|
import { SecretVersion } from "../ee/models";
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +19,7 @@ import {
|
|||||||
InternalServerError,
|
InternalServerError,
|
||||||
SecretBlindIndexDataNotFoundError,
|
SecretBlindIndexDataNotFoundError,
|
||||||
SecretNotFoundError,
|
SecretNotFoundError,
|
||||||
UnauthorizedRequestError,
|
UnauthorizedRequestError
|
||||||
} from "../utils/errors";
|
} from "../utils/errors";
|
||||||
import {
|
import {
|
||||||
ACTION_ADD_SECRETS,
|
ACTION_ADD_SECRETS,
|
||||||
@@ -29,51 +30,65 @@ import {
|
|||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
ENCODING_SCHEME_UTF8,
|
ENCODING_SCHEME_UTF8,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import * as argon2 from "argon2";
|
import * as argon2 from "argon2";
|
||||||
import {
|
import {
|
||||||
decryptSymmetric128BitHexKeyUTF8,
|
decryptSymmetric128BitHexKeyUTF8,
|
||||||
encryptSymmetric128BitHexKeyUTF8,
|
encryptSymmetric128BitHexKeyUTF8
|
||||||
} from "../utils/crypto";
|
} from "../utils/crypto";
|
||||||
import { TelemetryService } from "../services";
|
import { TelemetryService } from "../services";
|
||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { EELogService, EESecretService } from "../ee/services";
|
import { EELogService, EESecretService } from "../ee/services";
|
||||||
import {
|
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth";
|
||||||
getAuthDataPayloadIdObj,
|
|
||||||
getAuthDataPayloadUserObj,
|
|
||||||
} from "../utils/auth";
|
|
||||||
import { getFolderIdFromServiceToken } from "../services/FolderService";
|
import { getFolderIdFromServiceToken } from "../services/FolderService";
|
||||||
|
import picomatch from "picomatch";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export const isValidScope = (
|
||||||
|
authPayload: IServiceTokenData,
|
||||||
|
environment: string,
|
||||||
|
secretPath: string
|
||||||
|
) => {
|
||||||
|
const { scopes: tkScopes } = authPayload;
|
||||||
|
const validScope = tkScopes.find(
|
||||||
|
(scope) =>
|
||||||
|
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
|
||||||
|
scope.environment === environment
|
||||||
|
);
|
||||||
|
|
||||||
|
return Boolean(validScope);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function containsGlobPatterns(secretPath: string) {
|
||||||
|
const globChars = ["*", "?", "[", "]", "{", "}", "**"];
|
||||||
|
const normalizedPath = path.normalize(secretPath);
|
||||||
|
return globChars.some(char => normalizedPath.includes(char));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
||||||
*
|
*
|
||||||
* Precondition: the workspace for secret [secret] must have E2EE disabled
|
* Precondition: the workspace for secret [secret] must have E2EE disabled
|
||||||
* @param {ISecret} secret - secret to repackage to raw
|
* @param {ISecret} secret - secret to repackage to raw
|
||||||
* @param {String} key - symmetric key to use to decrypt secret
|
* @param {String} key - symmetric key to use to decrypt secret
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const repackageSecretToRaw = ({
|
export const repackageSecretToRaw = ({ secret, key }: { secret: ISecret; key: string }) => {
|
||||||
secret,
|
|
||||||
key,
|
|
||||||
}: {
|
|
||||||
secret: ISecret;
|
|
||||||
key: string;
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretKeyCiphertext,
|
ciphertext: secret.secretKeyCiphertext,
|
||||||
iv: secret.secretKeyIV,
|
iv: secret.secretKeyIV,
|
||||||
tag: secret.secretKeyTag,
|
tag: secret.secretKeyTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secret.secretValueCiphertext,
|
ciphertext: secret.secretValueCiphertext,
|
||||||
iv: secret.secretValueIV,
|
iv: secret.secretValueIV,
|
||||||
tag: secret.secretValueTag,
|
tag: secret.secretValueTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
let secretComment = "";
|
let secretComment = "";
|
||||||
@@ -83,11 +98,11 @@ export const repackageSecretToRaw = ({
|
|||||||
ciphertext: secret.secretCommentCiphertext,
|
ciphertext: secret.secretCommentCiphertext,
|
||||||
iv: secret.secretCommentIV,
|
iv: secret.secretCommentIV,
|
||||||
tag: secret.secretCommentTag,
|
tag: secret.secretCommentTag,
|
||||||
key,
|
key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ({
|
return {
|
||||||
_id: secret._id,
|
_id: secret._id,
|
||||||
version: secret.version,
|
version: secret.version,
|
||||||
workspace: secret.workspace,
|
workspace: secret.workspace,
|
||||||
@@ -96,9 +111,9 @@ export const repackageSecretToRaw = ({
|
|||||||
user: secret.user,
|
user: secret.user,
|
||||||
secretKey,
|
secretKey,
|
||||||
secretValue,
|
secretValue,
|
||||||
secretComment,
|
secretComment
|
||||||
});
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create secret blind index data containing encrypted blind index [salt]
|
* Create secret blind index data containing encrypted blind index [salt]
|
||||||
@@ -107,7 +122,7 @@ export const repackageSecretToRaw = ({
|
|||||||
* @param {Types.ObjectId} obj.workspaceId
|
* @param {Types.ObjectId} obj.workspaceId
|
||||||
*/
|
*/
|
||||||
export const createSecretBlindIndexDataHelper = async ({
|
export const createSecretBlindIndexDataHelper = async ({
|
||||||
workspaceId,
|
workspaceId
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -121,7 +136,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
|||||||
const {
|
const {
|
||||||
ciphertext: encryptedSaltCiphertext,
|
ciphertext: encryptedSaltCiphertext,
|
||||||
iv: saltIV,
|
iv: saltIV,
|
||||||
tag: saltTag,
|
tag: saltTag
|
||||||
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||||
|
|
||||||
return await new SecretBlindIndexData({
|
return await new SecretBlindIndexData({
|
||||||
@@ -130,16 +145,16 @@ export const createSecretBlindIndexDataHelper = async ({
|
|||||||
saltIV,
|
saltIV,
|
||||||
saltTag,
|
saltTag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_BASE64,
|
keyEncoding: ENCODING_SCHEME_BASE64
|
||||||
}).save();
|
}).save();
|
||||||
} else {
|
} else {
|
||||||
const {
|
const {
|
||||||
ciphertext: encryptedSaltCiphertext,
|
ciphertext: encryptedSaltCiphertext,
|
||||||
iv: saltIV,
|
iv: saltIV,
|
||||||
tag: saltTag,
|
tag: saltTag
|
||||||
} = encryptSymmetric128BitHexKeyUTF8({
|
} = encryptSymmetric128BitHexKeyUTF8({
|
||||||
plaintext: salt,
|
plaintext: salt,
|
||||||
key: encryptionKey,
|
key: encryptionKey
|
||||||
});
|
});
|
||||||
|
|
||||||
return await new SecretBlindIndexData({
|
return await new SecretBlindIndexData({
|
||||||
@@ -148,7 +163,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
|||||||
saltIV,
|
saltIV,
|
||||||
saltTag,
|
saltTag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -160,7 +175,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSecretBlindIndexSaltHelper = async ({
|
export const getSecretBlindIndexSaltHelper = async ({
|
||||||
workspaceId,
|
workspaceId
|
||||||
}: {
|
}: {
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -168,36 +183,30 @@ export const getSecretBlindIndexSaltHelper = async ({
|
|||||||
const rootEncryptionKey = await getRootEncryptionKey();
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).select("+algorithm +keyEncoding");
|
}).select("+algorithm +keyEncoding");
|
||||||
|
|
||||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||||
|
|
||||||
if (
|
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||||
rootEncryptionKey &&
|
|
||||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64
|
|
||||||
) {
|
|
||||||
return client.decryptSymmetric(
|
return client.decryptSymmetric(
|
||||||
secretBlindIndexData.encryptedSaltCiphertext,
|
secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
rootEncryptionKey,
|
rootEncryptionKey,
|
||||||
secretBlindIndexData.saltIV,
|
secretBlindIndexData.saltIV,
|
||||||
secretBlindIndexData.saltTag
|
secretBlindIndexData.saltTag
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
encryptionKey &&
|
|
||||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8
|
|
||||||
) {
|
|
||||||
// decrypt workspace salt
|
// decrypt workspace salt
|
||||||
return decryptSymmetric128BitHexKeyUTF8({
|
return decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
iv: secretBlindIndexData.saltIV,
|
iv: secretBlindIndexData.saltIV,
|
||||||
tag: secretBlindIndexData.saltTag,
|
tag: secretBlindIndexData.saltTag,
|
||||||
key: encryptionKey,
|
key: encryptionKey
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw InternalServerError({
|
throw InternalServerError({
|
||||||
message: "Failed to obtain workspace salt needed for secret blind indexing",
|
message: "Failed to obtain workspace salt needed for secret blind indexing"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -210,7 +219,7 @@ export const getSecretBlindIndexSaltHelper = async ({
|
|||||||
*/
|
*/
|
||||||
export const generateSecretBlindIndexWithSaltHelper = async ({
|
export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||||
secretName,
|
secretName,
|
||||||
salt,
|
salt
|
||||||
}: {
|
}: {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
salt: string;
|
salt: string;
|
||||||
@@ -224,7 +233,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
|
|||||||
memoryCost: 65536, // default pool of 64 MiB per thread.
|
memoryCost: 65536, // default pool of 64 MiB per thread.
|
||||||
hashLength: 32,
|
hashLength: 32,
|
||||||
parallelism: 1,
|
parallelism: 1,
|
||||||
raw: true,
|
raw: true
|
||||||
})
|
})
|
||||||
).toString("base64");
|
).toString("base64");
|
||||||
|
|
||||||
@@ -240,7 +249,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
|
|||||||
*/
|
*/
|
||||||
export const generateSecretBlindIndexHelper = async ({
|
export const generateSecretBlindIndexHelper = async ({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId,
|
workspaceId
|
||||||
}: {
|
}: {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
@@ -250,16 +259,13 @@ export const generateSecretBlindIndexHelper = async ({
|
|||||||
const rootEncryptionKey = await getRootEncryptionKey();
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
|
||||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).select("+algorithm +keyEncoding");
|
}).select("+algorithm +keyEncoding");
|
||||||
|
|
||||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||||
|
|
||||||
let salt;
|
let salt;
|
||||||
if (
|
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||||
rootEncryptionKey &&
|
|
||||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64
|
|
||||||
) {
|
|
||||||
salt = client.decryptSymmetric(
|
salt = client.decryptSymmetric(
|
||||||
secretBlindIndexData.encryptedSaltCiphertext,
|
secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
rootEncryptionKey,
|
rootEncryptionKey,
|
||||||
@@ -269,32 +275,29 @@ export const generateSecretBlindIndexHelper = async ({
|
|||||||
|
|
||||||
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||||
secretName,
|
secretName,
|
||||||
salt,
|
salt
|
||||||
});
|
});
|
||||||
|
|
||||||
return secretBlindIndex;
|
return secretBlindIndex;
|
||||||
} else if (
|
} else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||||
encryptionKey &&
|
|
||||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8
|
|
||||||
) {
|
|
||||||
// decrypt workspace salt
|
// decrypt workspace salt
|
||||||
salt = decryptSymmetric128BitHexKeyUTF8({
|
salt = decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||||
iv: secretBlindIndexData.saltIV,
|
iv: secretBlindIndexData.saltIV,
|
||||||
tag: secretBlindIndexData.saltTag,
|
tag: secretBlindIndexData.saltTag,
|
||||||
key: encryptionKey,
|
key: encryptionKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||||
secretName,
|
secretName,
|
||||||
salt,
|
salt
|
||||||
});
|
});
|
||||||
|
|
||||||
return secretBlindIndex;
|
return secretBlindIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw InternalServerError({
|
throw InternalServerError({
|
||||||
message: "Failed to generate secret blind index",
|
message: "Failed to generate secret blind index"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -323,38 +326,32 @@ export const createSecretHelper = async ({
|
|||||||
secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
secretPath = "/",
|
secretPath = "/"
|
||||||
}: CreateSecretParams) => {
|
}: CreateSecretParams) => {
|
||||||
|
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// if using service token filter towards the folderId by secretpath
|
// if using service token filter towards the folderId by secretpath
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||||
if (secretPath !== serviceTkScopedSecretPath) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const folderId = await getFolderIdFromServiceToken(
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
|
|
||||||
const exists = await Secret.exists({
|
const exists = await Secret.exists({
|
||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type,
|
type,
|
||||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exists)
|
if (exists)
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message: "Failed to create secret that already exists",
|
message: "Failed to create secret that already exists"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (type === SECRET_PERSONAL) {
|
if (type === SECRET_PERSONAL) {
|
||||||
@@ -365,13 +362,12 @@ export const createSecretHelper = async ({
|
|||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
type: SECRET_SHARED,
|
type: SECRET_SHARED
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!exists)
|
if (!exists)
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message:
|
message: "Failed to create personal secret override for no corresponding shared secret"
|
||||||
"Failed to create personal secret override for no corresponding shared secret",
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +390,7 @@ export const createSecretHelper = async ({
|
|||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
const secretVersion = new SecretVersion({
|
const secretVersion = new SecretVersion({
|
||||||
@@ -414,12 +410,12 @@ export const createSecretHelper = async ({
|
|||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) add version for new secret
|
// (EE) add version for new secret
|
||||||
await EESecretService.addSecretVersions({
|
await EESecretService.addSecretVersions({
|
||||||
secretVersions: [secretVersion],
|
secretVersions: [secretVersion]
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -427,7 +423,7 @@ export const createSecretHelper = async ({
|
|||||||
name: ACTION_ADD_SECRETS,
|
name: ACTION_ADD_SECRETS,
|
||||||
...getAuthDataPayloadIdObj(authData),
|
...getAuthDataPayloadIdObj(authData),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds: [secret._id],
|
secretIds: [secret._id]
|
||||||
});
|
});
|
||||||
|
|
||||||
action &&
|
action &&
|
||||||
@@ -436,14 +432,14 @@ export const createSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
actions: [action],
|
actions: [action],
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
ipAddress: authData.authIP,
|
ipAddress: authData.authIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId,
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -452,7 +448,7 @@ export const createSecretHelper = async ({
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets added",
|
event: "secrets added",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData,
|
authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: 1,
|
numberOfSecrets: 1,
|
||||||
@@ -460,8 +456,8 @@ export const createSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
userAgent: authData.authUserAgent,
|
userAgent: authData.authUserAgent
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,21 +476,16 @@ export const getSecretsHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
authData,
|
authData,
|
||||||
secretPath = "/",
|
secretPath = "/"
|
||||||
}: GetSecretsParams) => {
|
}: GetSecretsParams) => {
|
||||||
let secrets: ISecret[] = [];
|
let secrets: ISecret[] = [];
|
||||||
// if using service token filter towards the folderId by secretpath
|
// if using service token filter towards the folderId by secretpath
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||||
if (secretPath !== serviceTkScopedSecretPath) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const folderId = await getFolderIdFromServiceToken(
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
|
|
||||||
// get personal secrets first
|
// get personal secrets first
|
||||||
secrets = await Secret.find({
|
secrets = await Secret.find({
|
||||||
@@ -502,8 +493,10 @@ export const getSecretsHelper = async ({
|
|||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type: SECRET_PERSONAL,
|
type: SECRET_PERSONAL,
|
||||||
...getAuthDataPayloadUserObj(authData),
|
...getAuthDataPayloadUserObj(authData)
|
||||||
}).populate("tags").lean();
|
})
|
||||||
|
.populate("tags")
|
||||||
|
.lean();
|
||||||
|
|
||||||
// concat with shared secrets
|
// concat with shared secrets
|
||||||
secrets = secrets.concat(
|
secrets = secrets.concat(
|
||||||
@@ -513,9 +506,11 @@ export const getSecretsHelper = async ({
|
|||||||
folder: folderId,
|
folder: folderId,
|
||||||
type: SECRET_SHARED,
|
type: SECRET_SHARED,
|
||||||
secretBlindIndex: {
|
secretBlindIndex: {
|
||||||
$nin: secrets.map((secret) => secret.secretBlindIndex),
|
$nin: secrets.map((secret) => secret.secretBlindIndex)
|
||||||
},
|
}
|
||||||
}).populate("tags").lean()
|
})
|
||||||
|
.populate("tags")
|
||||||
|
.lean()
|
||||||
);
|
);
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -523,7 +518,7 @@ export const getSecretsHelper = async ({
|
|||||||
name: ACTION_READ_SECRETS,
|
name: ACTION_READ_SECRETS,
|
||||||
...getAuthDataPayloadIdObj(authData),
|
...getAuthDataPayloadIdObj(authData),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds: secrets.map((secret) => secret._id),
|
secretIds: secrets.map((secret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
action &&
|
action &&
|
||||||
@@ -532,7 +527,7 @@ export const getSecretsHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
actions: [action],
|
actions: [action],
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
ipAddress: authData.authIP,
|
ipAddress: authData.authIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -541,7 +536,7 @@ export const getSecretsHelper = async ({
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets pulled",
|
event: "secrets pulled",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData,
|
authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
@@ -549,8 +544,8 @@ export const getSecretsHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
userAgent: authData.authUserAgent,
|
userAgent: authData.authUserAgent
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,25 +568,20 @@ export const getSecretHelper = async ({
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
authData,
|
authData,
|
||||||
secretPath = "/",
|
secretPath = "/"
|
||||||
}: GetSecretParams) => {
|
}: GetSecretParams) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
let secret: ISecret | null = null;
|
let secret: ISecret | null = null;
|
||||||
// if using service token filter towards the folderId by secretpath
|
// if using service token filter towards the folderId by secretpath
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||||
if (secretPath !== serviceTkScopedSecretPath) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const folderId = await getFolderIdFromServiceToken(
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
|
|
||||||
// try getting personal secret first (if exists)
|
// try getting personal secret first (if exists)
|
||||||
secret = await Secret.findOne({
|
secret = await Secret.findOne({
|
||||||
@@ -600,7 +590,7 @@ export const getSecretHelper = async ({
|
|||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type: type ?? SECRET_PERSONAL,
|
type: type ?? SECRET_PERSONAL,
|
||||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
@@ -611,7 +601,7 @@ export const getSecretHelper = async ({
|
|||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type: SECRET_SHARED,
|
type: SECRET_SHARED
|
||||||
}).lean();
|
}).lean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,7 +612,7 @@ export const getSecretHelper = async ({
|
|||||||
name: ACTION_READ_SECRETS,
|
name: ACTION_READ_SECRETS,
|
||||||
...getAuthDataPayloadIdObj(authData),
|
...getAuthDataPayloadIdObj(authData),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds: [secret._id],
|
secretIds: [secret._id]
|
||||||
});
|
});
|
||||||
|
|
||||||
action &&
|
action &&
|
||||||
@@ -631,7 +621,7 @@ export const getSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
actions: [action],
|
actions: [action],
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
ipAddress: authData.authIP,
|
ipAddress: authData.authIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -640,7 +630,7 @@ export const getSecretHelper = async ({
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets pull",
|
event: "secrets pull",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData,
|
authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: 1,
|
numberOfSecrets: 1,
|
||||||
@@ -648,8 +638,8 @@ export const getSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
userAgent: authData.authUserAgent,
|
userAgent: authData.authUserAgent
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,26 +669,21 @@ export const updateSecretHelper = async ({
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
secretPath,
|
secretPath
|
||||||
}: UpdateSecretParams) => {
|
}: UpdateSecretParams) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
let secret: ISecret | null = null;
|
let secret: ISecret | null = null;
|
||||||
// if using service token filter towards the folderId by secretpath
|
// if using service token filter towards the folderId by secretpath
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||||
if (secretPath !== serviceTkScopedSecretPath) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const folderId = await getFolderIdFromServiceToken(
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
|
|
||||||
if (type === SECRET_SHARED) {
|
if (type === SECRET_SHARED) {
|
||||||
// case: update shared secret
|
// case: update shared secret
|
||||||
@@ -708,16 +693,16 @@ export const updateSecretHelper = async ({
|
|||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type,
|
type
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
$inc: { version: 1 },
|
$inc: { version: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -730,16 +715,16 @@ export const updateSecretHelper = async ({
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
...getAuthDataPayloadUserObj(authData),
|
...getAuthDataPayloadUserObj(authData)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
$inc: { version: 1 },
|
$inc: { version: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -763,12 +748,12 @@ export const updateSecretHelper = async ({
|
|||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) add version for new secret
|
// (EE) add version for new secret
|
||||||
await EESecretService.addSecretVersions({
|
await EESecretService.addSecretVersions({
|
||||||
secretVersions: [secretVersion],
|
secretVersions: [secretVersion]
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -776,7 +761,7 @@ export const updateSecretHelper = async ({
|
|||||||
name: ACTION_UPDATE_SECRETS,
|
name: ACTION_UPDATE_SECRETS,
|
||||||
...getAuthDataPayloadIdObj(authData),
|
...getAuthDataPayloadIdObj(authData),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds: [secret._id],
|
secretIds: [secret._id]
|
||||||
});
|
});
|
||||||
|
|
||||||
action &&
|
action &&
|
||||||
@@ -785,14 +770,14 @@ export const updateSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
actions: [action],
|
actions: [action],
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
ipAddress: authData.authIP,
|
ipAddress: authData.authIP
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId: secret?.folder,
|
folderId: secret?.folder
|
||||||
});
|
});
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -801,7 +786,7 @@ export const updateSecretHelper = async ({
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets modified",
|
event: "secrets modified",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData,
|
authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: 1,
|
numberOfSecrets: 1,
|
||||||
@@ -809,8 +794,8 @@ export const updateSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
userAgent: authData.authUserAgent,
|
userAgent: authData.authUserAgent
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,26 +818,20 @@ export const deleteSecretHelper = async ({
|
|||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
authData,
|
authData,
|
||||||
secretPath = "/",
|
secretPath = "/"
|
||||||
}: DeleteSecretParams) => {
|
}: DeleteSecretParams) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
// if using service token filter towards the folderId by secretpath
|
// if using service token filter towards the folderId by secretpath
|
||||||
if (authData.authPayload instanceof ServiceTokenData) {
|
if (authData.authPayload instanceof ServiceTokenData) {
|
||||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||||
|
|
||||||
if (secretPath !== serviceTkScopedSecretPath) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const folderId = await getFolderIdFromServiceToken(
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
workspaceId,
|
|
||||||
environment,
|
|
||||||
secretPath
|
|
||||||
);
|
|
||||||
|
|
||||||
let secrets: ISecret[] = [];
|
let secrets: ISecret[] = [];
|
||||||
let secret: ISecret | null = null;
|
let secret: ISecret | null = null;
|
||||||
@@ -862,7 +841,7 @@ export const deleteSecretHelper = async ({
|
|||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
secret = await Secret.findOneAndDelete({
|
secret = await Secret.findOneAndDelete({
|
||||||
@@ -870,14 +849,14 @@ export const deleteSecretHelper = async ({
|
|||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
folder: folderId,
|
folder: folderId
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
await Secret.deleteMany({
|
await Secret.deleteMany({
|
||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
secret = await Secret.findOneAndDelete({
|
secret = await Secret.findOneAndDelete({
|
||||||
@@ -886,7 +865,7 @@ export const deleteSecretHelper = async ({
|
|||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
type,
|
type,
|
||||||
...getAuthDataPayloadUserObj(authData),
|
...getAuthDataPayloadUserObj(authData)
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
if (secret) {
|
if (secret) {
|
||||||
@@ -897,7 +876,7 @@ export const deleteSecretHelper = async ({
|
|||||||
if (!secret) throw SecretNotFoundError();
|
if (!secret) throw SecretNotFoundError();
|
||||||
|
|
||||||
await EESecretService.markDeletedSecretVersions({
|
await EESecretService.markDeletedSecretVersions({
|
||||||
secretIds: secrets.map((secret) => secret._id),
|
secretIds: secrets.map((secret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
@@ -905,22 +884,23 @@ export const deleteSecretHelper = async ({
|
|||||||
name: ACTION_DELETE_SECRETS,
|
name: ACTION_DELETE_SECRETS,
|
||||||
...getAuthDataPayloadIdObj(authData),
|
...getAuthDataPayloadIdObj(authData),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds: secrets.map((secret) => secret._id),
|
secretIds: secrets.map((secret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
action && (await EELogService.createLog({
|
action &&
|
||||||
...getAuthDataPayloadIdObj(authData),
|
(await EELogService.createLog({
|
||||||
workspaceId,
|
...getAuthDataPayloadIdObj(authData),
|
||||||
actions: [action],
|
workspaceId,
|
||||||
channel: authData.authChannel,
|
actions: [action],
|
||||||
ipAddress: authData.authIP,
|
channel: authData.authChannel,
|
||||||
}));
|
ipAddress: authData.authIP
|
||||||
|
}));
|
||||||
|
|
||||||
// (EE) take a secret snapshot
|
// (EE) take a secret snapshot
|
||||||
await EESecretService.takeSecretSnapshot({
|
await EESecretService.takeSecretSnapshot({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId: secret?.folder,
|
folderId: secret?.folder
|
||||||
});
|
});
|
||||||
|
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
@@ -929,7 +909,7 @@ export const deleteSecretHelper = async ({
|
|||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: "secrets deleted",
|
event: "secrets deleted",
|
||||||
distinctId: await TelemetryService.getDistinctId({
|
distinctId: await TelemetryService.getDistinctId({
|
||||||
authData,
|
authData
|
||||||
}),
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
@@ -937,13 +917,13 @@ export const deleteSecretHelper = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
channel: authData.authChannel,
|
channel: authData.authChannel,
|
||||||
userAgent: authData.authUserAgent,
|
userAgent: authData.authUserAgent
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ({
|
return {
|
||||||
secrets,
|
secrets,
|
||||||
secret,
|
secret
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import {
|
|||||||
Secret,
|
Secret,
|
||||||
Workspace,
|
Workspace,
|
||||||
} from "../models";
|
} from "../models";
|
||||||
|
import {
|
||||||
|
IPType,
|
||||||
|
TrustedIP
|
||||||
|
} from "../ee/models";
|
||||||
import { createBot } from "../helpers/bot";
|
import { createBot } from "../helpers/bot";
|
||||||
import { EELicenseService } from "../ee/services";
|
import { EELicenseService } from "../ee/services";
|
||||||
import { SecretService } from "../services";
|
import { SecretService } from "../services";
|
||||||
@@ -40,6 +44,16 @@ export const createWorkspace = async ({
|
|||||||
await SecretService.createSecretBlindIndexData({
|
await SecretService.createSecretBlindIndexData({
|
||||||
workspaceId: workspace._id,
|
workspaceId: workspace._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// initialize default trusted ip of 0.0.0.0/0
|
||||||
|
await new TrustedIP({
|
||||||
|
workspace: workspace._id,
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: IPType.IPV4,
|
||||||
|
prefix: 0,
|
||||||
|
isActive: true,
|
||||||
|
comment: ""
|
||||||
|
}).save()
|
||||||
|
|
||||||
await EELicenseService.refreshPlan(organizationId);
|
await EELicenseService.refreshPlan(organizationId);
|
||||||
|
|
||||||
|
|||||||
+46
-14
@@ -5,11 +5,12 @@ import express from "express";
|
|||||||
require("express-async-errors");
|
require("express-async-errors");
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import { DatabaseService } from "./services";
|
import { DatabaseService, GithubSecretScanningService } from "./services";
|
||||||
import { EELicenseService } from "./ee/services";
|
import { EELicenseService } from "./ee/services";
|
||||||
import { setUpHealthEndpoint } from "./services/health";
|
import { setUpHealthEndpoint } from "./services/health";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import swaggerUi = require("swagger-ui-express");
|
import swaggerUi = require("swagger-ui-express");
|
||||||
|
import { Probot, createNodeMiddleware } from "probot";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const swaggerFile = require("../spec.json");
|
const swaggerFile = require("../spec.json");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -18,8 +19,10 @@ import {
|
|||||||
action as eeActionRouter,
|
action as eeActionRouter,
|
||||||
cloudProducts as eeCloudProductsRouter,
|
cloudProducts as eeCloudProductsRouter,
|
||||||
organizations as eeOrganizationsRouter,
|
organizations as eeOrganizationsRouter,
|
||||||
|
sso as eeSSORouter,
|
||||||
secret as eeSecretRouter,
|
secret as eeSecretRouter,
|
||||||
secretSnapshot as eeSecretSnapshotRouter,
|
secretSnapshot as eeSecretSnapshotRouter,
|
||||||
|
users as eeUsersRouter,
|
||||||
workspace as eeWorkspaceRouter,
|
workspace as eeWorkspaceRouter,
|
||||||
} from "./ee/routes/v1";
|
} from "./ee/routes/v1";
|
||||||
import {
|
import {
|
||||||
@@ -33,41 +36,46 @@ import {
|
|||||||
membership as v1MembershipRouter,
|
membership as v1MembershipRouter,
|
||||||
organization as v1OrganizationRouter,
|
organization as v1OrganizationRouter,
|
||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
|
secretImport as v1SecretImportRouter,
|
||||||
secret as v1SecretRouter,
|
secret as v1SecretRouter,
|
||||||
|
secretScanning as v1SecretScanningRouter,
|
||||||
secretsFolder as v1SecretsFolder,
|
secretsFolder as v1SecretsFolder,
|
||||||
serviceToken as v1ServiceTokenRouter,
|
serviceToken as v1ServiceTokenRouter,
|
||||||
signup as v1SignupRouter,
|
signup as v1SignupRouter,
|
||||||
userAction as v1UserActionRouter,
|
userAction as v1UserActionRouter,
|
||||||
user as v1UserRouter,
|
user as v1UserRouter,
|
||||||
workspace as v1WorkspaceRouter,
|
webhooks as v1WebhooksRouter,
|
||||||
|
workspace as v1WorkspaceRouter
|
||||||
} from "./routes/v1";
|
} from "./routes/v1";
|
||||||
import {
|
import {
|
||||||
signup as v2SignupRouter,
|
|
||||||
auth as v2AuthRouter,
|
auth as v2AuthRouter,
|
||||||
users as v2UsersRouter,
|
environment as v2EnvironmentRouter,
|
||||||
organizations as v2OrganizationsRouter,
|
organizations as v2OrganizationsRouter,
|
||||||
workspace as v2WorkspaceRouter,
|
|
||||||
secret as v2SecretRouter, // begin to phase out
|
secret as v2SecretRouter, // begin to phase out
|
||||||
secrets as v2SecretsRouter,
|
secrets as v2SecretsRouter,
|
||||||
serviceTokenData as v2ServiceTokenDataRouter,
|
|
||||||
serviceAccounts as v2ServiceAccountsRouter,
|
serviceAccounts as v2ServiceAccountsRouter,
|
||||||
environment as v2EnvironmentRouter,
|
serviceTokenData as v2ServiceTokenDataRouter,
|
||||||
|
signup as v2SignupRouter,
|
||||||
tags as v2TagsRouter,
|
tags as v2TagsRouter,
|
||||||
|
users as v2UsersRouter,
|
||||||
|
workspace as v2WorkspaceRouter,
|
||||||
} from "./routes/v2";
|
} from "./routes/v2";
|
||||||
import {
|
import {
|
||||||
auth as v3AuthRouter,
|
auth as v3AuthRouter,
|
||||||
secrets as v3SecretsRouter,
|
secrets as v3SecretsRouter,
|
||||||
signup as v3SignupRouter,
|
signup as v3SignupRouter,
|
||||||
workspaces as v3WorkspacesRouter,
|
workspaces as v3WorkspacesRouter
|
||||||
} from "./routes/v3";
|
} from "./routes/v3";
|
||||||
import { healthCheck } from "./routes/status";
|
import { healthCheck } from "./routes/status";
|
||||||
import { getLogger } from "./utils/logger";
|
import { getLogger } from "./utils/logger";
|
||||||
import { RouteNotFoundError } from "./utils/errors";
|
import { RouteNotFoundError } from "./utils/errors";
|
||||||
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
||||||
import { getNodeEnv, getPort, getSiteURL } from "./config";
|
import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config";
|
||||||
import { setup } from "./utils/setup";
|
import { setup } from "./utils/setup";
|
||||||
|
const SmeeClient = require('smee-client') // eslint-disable-line
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
|
||||||
await setup();
|
await setup();
|
||||||
|
|
||||||
await EELicenseService.initGlobalFeatureSet();
|
await EELicenseService.initGlobalFeatureSet();
|
||||||
@@ -75,14 +83,35 @@ const main = async () => {
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.enable("trust proxy");
|
app.enable("trust proxy");
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
credentials: true,
|
credentials: true,
|
||||||
origin: await getSiteURL(),
|
origin: await getSiteURL()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey()) {
|
||||||
|
const probot = new Probot({
|
||||||
|
appId: await getSecretScanningGitAppId(),
|
||||||
|
privateKey: await getSecretScanningPrivateKey(),
|
||||||
|
secret: await getSecretScanningWebhookSecret(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((await getNodeEnv()) != "production") {
|
||||||
|
const smee = new SmeeClient({
|
||||||
|
source: await getSecretScanningWebhookProxy(),
|
||||||
|
target: "http://backend:4000/ss-webhook",
|
||||||
|
logger: console
|
||||||
|
})
|
||||||
|
|
||||||
|
smee.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })); // secret scanning webhook
|
||||||
|
}
|
||||||
|
|
||||||
if ((await getNodeEnv()) === "production") {
|
if ((await getNodeEnv()) === "production") {
|
||||||
// enable app-wide rate-limiting + helmet security
|
// enable app-wide rate-limiting + helmet security
|
||||||
// in production
|
// in production
|
||||||
@@ -101,9 +130,11 @@ const main = async () => {
|
|||||||
// (EE) routes
|
// (EE) routes
|
||||||
app.use("/api/v1/secret", eeSecretRouter);
|
app.use("/api/v1/secret", eeSecretRouter);
|
||||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||||
|
app.use("/api/v1/users", eeUsersRouter);
|
||||||
app.use("/api/v1/workspace", eeWorkspaceRouter);
|
app.use("/api/v1/workspace", eeWorkspaceRouter);
|
||||||
app.use("/api/v1/action", eeActionRouter);
|
app.use("/api/v1/action", eeActionRouter);
|
||||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||||
|
app.use("/api/v1/sso", eeSSORouter);
|
||||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||||
|
|
||||||
// v1 routes (default)
|
// v1 routes (default)
|
||||||
@@ -124,6 +155,9 @@ const main = async () => {
|
|||||||
app.use("/api/v1/integration", v1IntegrationRouter);
|
app.use("/api/v1/integration", v1IntegrationRouter);
|
||||||
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
|
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
|
||||||
app.use("/api/v1/folders", v1SecretsFolder);
|
app.use("/api/v1/folders", v1SecretsFolder);
|
||||||
|
app.use("/api/v1/secret-scanning", v1SecretScanningRouter);
|
||||||
|
app.use("/api/v1/webhooks", v1WebhooksRouter);
|
||||||
|
app.use("/api/v1/secret-imports", v1SecretImportRouter);
|
||||||
|
|
||||||
// v2 routes (improvements)
|
// v2 routes (improvements)
|
||||||
app.use("/api/v2/signup", v2SignupRouter);
|
app.use("/api/v2/signup", v2SignupRouter);
|
||||||
@@ -155,7 +189,7 @@ const main = async () => {
|
|||||||
if (res.headersSent) return next();
|
if (res.headersSent) return next();
|
||||||
next(
|
next(
|
||||||
RouteNotFoundError({
|
RouteNotFoundError({
|
||||||
message: `The requested source '(${req.method})${req.url}' was not found`,
|
message: `The requested source '(${req.method})${req.url}' was not found`
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -163,9 +197,7 @@ const main = async () => {
|
|||||||
app.use(requestErrorHandler);
|
app.use(requestErrorHandler);
|
||||||
|
|
||||||
const server = app.listen(await getPort(), async () => {
|
const server = app.listen(await getPort(), async () => {
|
||||||
(await getLogger("backend-main")).info(
|
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`);
|
||||||
`Server started listening at port ${await getPort()}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// await createTestUserForDevelopment();
|
// await createTestUserForDevelopment();
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import { IIntegrationAuth } from "../models";
|
|
||||||
import { standardRequest } from "../config/request";
|
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_API_URL,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_CHECKLY_API_URL,
|
INTEGRATION_CHECKLY_API_URL,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_CIRCLECI_API_URL,
|
INTEGRATION_CIRCLECI_API_URL,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
|
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
|
INTEGRATION_CLOUD_66_API_URL,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_CODEFRESH_API_URL,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_API_URL,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_FLYIO_API_URL,
|
INTEGRATION_FLYIO_API_URL,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
@@ -33,8 +38,11 @@ import {
|
|||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
INTEGRATION_TRAVISCI_API_URL,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_VERCEL_API_URL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
import { IIntegrationAuth } from "../models";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
import { standardRequest } from "../config/request";
|
||||||
|
|
||||||
interface App {
|
interface App {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -56,11 +64,13 @@ const getApps = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
accessId,
|
accessId,
|
||||||
teamId,
|
teamId,
|
||||||
|
workspaceSlug,
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
accessId?: string;
|
accessId?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
|
workspaceSlug?: string;
|
||||||
}) => {
|
}) => {
|
||||||
let apps: App[] = [];
|
let apps: App[] = [];
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
@@ -153,6 +163,27 @@ const getApps = async ({
|
|||||||
accountId: accessId
|
accountId: accessId
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_BITBUCKET:
|
||||||
|
apps = await getAppsBitBucket({
|
||||||
|
accessToken,
|
||||||
|
workspaceSlug
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case INTEGRATION_CODEFRESH:
|
||||||
|
apps = await getAppsCodefresh({
|
||||||
|
accessToken,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM:
|
||||||
|
apps = await getAppsDigitalOceanAppPlatform({
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case INTEGRATION_CLOUD_66:
|
||||||
|
apps = await getAppsCloud66({
|
||||||
|
accessToken,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
@@ -204,10 +235,10 @@ const getAppsVercel = async ({
|
|||||||
},
|
},
|
||||||
...(integrationAuth?.teamId
|
...(integrationAuth?.teamId
|
||||||
? {
|
? {
|
||||||
params: {
|
params: {
|
||||||
teamId: integrationAuth.teamId,
|
teamId: integrationAuth.teamId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
@@ -740,15 +771,76 @@ const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
* @returns {Object[]} apps - Cloudflare Pages projects
|
* @returns {Object[]} apps - Cloudflare Pages projects
|
||||||
* @returns {String} apps.name - name of Cloudflare Pages project
|
* @returns {String} apps.name - name of Cloudflare Pages project
|
||||||
*/
|
*/
|
||||||
const getAppsCloudflarePages = async ({
|
const getAppsCloudflarePages = async ({
|
||||||
accessToken,
|
accessToken,
|
||||||
accountId
|
accountId
|
||||||
}: {
|
}: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await standardRequest.get(
|
const { data } = await standardRequest.get(
|
||||||
`${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects`,
|
`${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const apps = data.result.map((a: any) => {
|
||||||
|
return {
|
||||||
|
name: a.name,
|
||||||
|
appId: a.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for BitBucket API
|
||||||
|
* @param {String} obj.workspaceSlug - Workspace identifier for fetching BitBucket repositories
|
||||||
|
* @returns {Object[]} apps - BitBucket repositories
|
||||||
|
* @returns {String} apps.name - name of BitBucket repository
|
||||||
|
*/
|
||||||
|
const getAppsBitBucket = async ({
|
||||||
|
accessToken,
|
||||||
|
workspaceSlug,
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
workspaceSlug?: string;
|
||||||
|
}) => {
|
||||||
|
interface RepositoriesResponse {
|
||||||
|
size: number;
|
||||||
|
page: number;
|
||||||
|
pageLen: number;
|
||||||
|
next: string;
|
||||||
|
previous: string;
|
||||||
|
values: Array<Repository>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Repository {
|
||||||
|
type: string;
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
is_private: boolean;
|
||||||
|
created_on: string;
|
||||||
|
updated_on: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!workspaceSlug) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const repositories: Repository[] = [];
|
||||||
|
let hasNextPage = true;
|
||||||
|
let repositoriesUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/repositories/${workspaceSlug}`
|
||||||
|
|
||||||
|
while (hasNextPage) {
|
||||||
|
const { data }: { data: RepositoriesResponse } = await standardRequest.get(
|
||||||
|
repositoriesUrl,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
@@ -757,13 +849,157 @@ const getAppsCloudflarePages = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const apps = data.result.map((a: any) => {
|
if (data?.values.length > 0) {
|
||||||
return {
|
data.values.forEach((repository) => {
|
||||||
name: a.name,
|
repositories.push(repository)
|
||||||
appId: a.id,
|
})
|
||||||
};
|
}
|
||||||
});
|
|
||||||
return apps;
|
if (data.next) {
|
||||||
|
repositoriesUrl = data.next
|
||||||
|
} else {
|
||||||
|
hasNextPage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apps = repositories.map((repository) => {
|
||||||
|
return {
|
||||||
|
name: repository.name,
|
||||||
|
appId: repository.uuid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of projects for Supabase integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for Supabase API
|
||||||
|
* @returns {Object[]} apps - names of Supabase apps
|
||||||
|
* @returns {String} apps.name - name of Supabase app
|
||||||
|
*/
|
||||||
|
const getAppsCodefresh = async ({
|
||||||
|
accessToken,
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
const res = (
|
||||||
|
await standardRequest.get(`${INTEGRATION_CODEFRESH_API_URL}/projects`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
const apps = res.projects.map((a: any) => ({
|
||||||
|
name: a.projectName,
|
||||||
|
appId: a.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of applications for DigitalOcean App Platform integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - personal access token for DigitalOcean
|
||||||
|
* @returns {Object[]} apps - names of DigitalOcean apps
|
||||||
|
* @returns {String} apps.name - name of DigitalOcean app
|
||||||
|
* @returns {String} apps.appId - id of DigitalOcean app
|
||||||
|
*/
|
||||||
|
const getAppsDigitalOceanAppPlatform = async ({ accessToken }: { accessToken: string }) => {
|
||||||
|
interface DigitalOceanApp {
|
||||||
|
id: string;
|
||||||
|
owner_uuid: string;
|
||||||
|
spec: Spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Spec {
|
||||||
|
name: string;
|
||||||
|
region: string;
|
||||||
|
envs: Env[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Env {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = (
|
||||||
|
await standardRequest.get(`${INTEGRATION_DIGITAL_OCEAN_API_URL}/v2/apps`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
return (res.apps ?? []).map((a: DigitalOceanApp) => ({
|
||||||
|
name: a.spec.name,
|
||||||
|
appId: a.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of applications for Cloud66 integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - personal access token for Cloud66 API
|
||||||
|
* @returns {Object[]} apps - Cloud66 apps
|
||||||
|
* @returns {String} apps.name - name of Cloud66 app
|
||||||
|
* @returns {String} apps.appId - uid of Cloud66 app
|
||||||
|
*/
|
||||||
|
const getAppsCloud66 = async ({ accessToken }: { accessToken: string }) => {
|
||||||
|
interface Cloud66Apps {
|
||||||
|
uid: string;
|
||||||
|
name: string;
|
||||||
|
account_id: number;
|
||||||
|
git: string;
|
||||||
|
git_branch: string;
|
||||||
|
environment: string;
|
||||||
|
cloud: string;
|
||||||
|
fqdn: string;
|
||||||
|
language: string;
|
||||||
|
framework: string;
|
||||||
|
status: number;
|
||||||
|
health: number;
|
||||||
|
last_activity: string;
|
||||||
|
last_activity_iso: string;
|
||||||
|
maintenance_mode: boolean;
|
||||||
|
has_loadbalancer: boolean;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
deploy_directory: string;
|
||||||
|
cloud_status: string;
|
||||||
|
backend: string;
|
||||||
|
version: string;
|
||||||
|
revision: string;
|
||||||
|
is_busy: boolean;
|
||||||
|
account_name: string;
|
||||||
|
is_cluster: boolean;
|
||||||
|
is_inside_cluster: boolean;
|
||||||
|
cluster_name: any;
|
||||||
|
application_address: string;
|
||||||
|
configstore_namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stacks = (
|
||||||
|
await standardRequest.get(`${INTEGRATION_CLOUD_66_API_URL}/3/stacks`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data.response as Cloud66Apps[]
|
||||||
|
|
||||||
|
const apps = stacks.map((app) => ({
|
||||||
|
name: app.name,
|
||||||
|
appId: app.uid
|
||||||
|
}));
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
export { getApps };
|
export { getApps };
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
|||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
@@ -15,11 +17,13 @@ import {
|
|||||||
} from "../variables";
|
} from "../variables";
|
||||||
import {
|
import {
|
||||||
getClientIdAzure,
|
getClientIdAzure,
|
||||||
|
getClientIdBitBucket,
|
||||||
getClientIdGitHub,
|
getClientIdGitHub,
|
||||||
getClientIdGitLab,
|
getClientIdGitLab,
|
||||||
getClientIdNetlify,
|
getClientIdNetlify,
|
||||||
getClientIdVercel,
|
getClientIdVercel,
|
||||||
getClientSecretAzure,
|
getClientSecretAzure,
|
||||||
|
getClientSecretBitBucket,
|
||||||
getClientSecretGitHub,
|
getClientSecretGitHub,
|
||||||
getClientSecretGitLab,
|
getClientSecretGitLab,
|
||||||
getClientSecretHeroku,
|
getClientSecretHeroku,
|
||||||
@@ -78,6 +82,15 @@ interface ExchangeCodeGitlabResponse {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExchangeCodeBitBucketResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
scopes: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
|
||||||
* code-token exchange for integration named [integration]
|
* code-token exchange for integration named [integration]
|
||||||
@@ -129,6 +142,12 @@ const exchangeCode = async ({
|
|||||||
obj = await exchangeCodeGitlab({
|
obj = await exchangeCodeGitlab({
|
||||||
code,
|
code,
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
|
case INTEGRATION_BITBUCKET:
|
||||||
|
obj = await exchangeCodeBitBucket({
|
||||||
|
code,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
@@ -347,4 +366,43 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for BitBucket
|
||||||
|
* code-token exchange
|
||||||
|
* @param {Object} obj1
|
||||||
|
* @param {Object} obj1.code - code for code-token exchange
|
||||||
|
* @returns {Object} obj2
|
||||||
|
* @returns {String} obj2.accessToken - access token for BitBucket API
|
||||||
|
* @returns {String} obj2.refreshToken - refresh token for BitBucket API
|
||||||
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
|
*/
|
||||||
|
const exchangeCodeBitBucket = async ({ code }: { code: string }) => {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
const res: ExchangeCodeBitBucketResponse = (
|
||||||
|
await standardRequest.post(
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code: code,
|
||||||
|
client_id: await getClientIdBitBucket(),
|
||||||
|
client_secret: await getClientSecretBitBucket(),
|
||||||
|
redirect_uri: `${await getSiteURL()}/integrations/bitbucket/oauth2/callback`,
|
||||||
|
} as any),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
|
refreshToken: res.refresh_token,
|
||||||
|
accessExpiresAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export { exchangeCode };
|
export { exchangeCode };
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
|||||||
import { IIntegrationAuth } from "../models";
|
import { IIntegrationAuth } from "../models";
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
@@ -13,8 +15,10 @@ import {
|
|||||||
import { IntegrationService } from "../services";
|
import { IntegrationService } from "../services";
|
||||||
import {
|
import {
|
||||||
getClientIdAzure,
|
getClientIdAzure,
|
||||||
|
getClientIdBitBucket,
|
||||||
getClientIdGitLab,
|
getClientIdGitLab,
|
||||||
getClientSecretAzure,
|
getClientSecretAzure,
|
||||||
|
getClientSecretBitBucket,
|
||||||
getClientSecretGitLab,
|
getClientSecretGitLab,
|
||||||
getClientSecretHeroku,
|
getClientSecretHeroku,
|
||||||
getSiteURL,
|
getSiteURL,
|
||||||
@@ -46,6 +50,15 @@ interface RefreshTokenGitLabResponse {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenBitBucketResponse {
|
||||||
|
access_token: string;
|
||||||
|
token_type: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
scopes: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -83,6 +96,11 @@ const exchangeRefresh = async ({
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_BITBUCKET:
|
||||||
|
tokenDetails = await exchangeRefreshBitBucket({
|
||||||
|
refreshToken,
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Failed to exchange token for incompatible integration");
|
throw new Error("Failed to exchange token for incompatible integration");
|
||||||
}
|
}
|
||||||
@@ -218,4 +236,46 @@ const exchangeRefreshGitLab = async ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
|
* BitBucket integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for BitBucket
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const exchangeRefreshBitBucket = async ({
|
||||||
|
refreshToken,
|
||||||
|
}: {
|
||||||
|
refreshToken: string;
|
||||||
|
}) => {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: RefreshTokenBitBucketResponse;
|
||||||
|
} = await standardRequest.post(
|
||||||
|
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
client_id: await getClientIdBitBucket(),
|
||||||
|
client_secret: await getClientSecretBitBucket(),
|
||||||
|
redirect_uri: `${await getSiteURL()}/integrations/bitbucket/oauth2/callback`,
|
||||||
|
} as any),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export { exchangeRefresh };
|
export { exchangeRefresh };
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ const revokeAccess = async ({
|
|||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let deletedIntegrationAuth;
|
|
||||||
// add any integration-specific revocation logic
|
// add any integration-specific revocation logic
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
@@ -33,7 +32,7 @@ const revokeAccess = async ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
||||||
_id: integrationAuth._id,
|
_id: integrationAuth._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+488
-168
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
import { Types } from "mongoose";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import {
|
import {
|
||||||
getAuthAPIKeyPayload,
|
getAuthAPIKeyPayload,
|
||||||
@@ -51,6 +52,10 @@ const requireAuth = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
let authUserPayload: {
|
||||||
|
user: IUser;
|
||||||
|
tokenVersionId: Types.ObjectId;
|
||||||
|
};
|
||||||
switch (authMode) {
|
switch (authMode) {
|
||||||
case AUTH_MODE_SERVICE_ACCOUNT:
|
case AUTH_MODE_SERVICE_ACCOUNT:
|
||||||
authPayload = await getAuthSAAKPayload({
|
authPayload = await getAuthSAAKPayload({
|
||||||
@@ -71,12 +76,12 @@ const requireAuth = ({
|
|||||||
req.user = authPayload;
|
req.user = authPayload;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const { user, tokenVersionId } = await getAuthUserPayload({
|
authUserPayload = await getAuthUserPayload({
|
||||||
authTokenValue,
|
authTokenValue,
|
||||||
});
|
});
|
||||||
authPayload = user;
|
authPayload = authUserPayload.user;
|
||||||
req.user = user;
|
req.user = authUserPayload.user;
|
||||||
req.tokenVersionId = tokenVersionId;
|
req.tokenVersionId = authUserPayload.tokenVersionId;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const requireWorkspaceAuth = ({
|
|||||||
requiredPermissions = [],
|
requiredPermissions = [],
|
||||||
requireBlindIndicesEnabled = false,
|
requireBlindIndicesEnabled = false,
|
||||||
requireE2EEOff = false,
|
requireE2EEOff = false,
|
||||||
|
checkIPAllowlist = false
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: Array<"admin" | "member">;
|
acceptedRoles: Array<"admin" | "member">;
|
||||||
locationWorkspaceId: req;
|
locationWorkspaceId: req;
|
||||||
@@ -25,6 +26,7 @@ const requireWorkspaceAuth = ({
|
|||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
requireBlindIndicesEnabled?: boolean;
|
requireBlindIndicesEnabled?: boolean;
|
||||||
requireE2EEOff?: boolean;
|
requireE2EEOff?: boolean;
|
||||||
|
checkIPAllowlist?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||||
@@ -39,6 +41,7 @@ const requireWorkspaceAuth = ({
|
|||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
requireBlindIndicesEnabled,
|
requireBlindIndicesEnabled,
|
||||||
requireE2EEOff,
|
requireE2EEOff,
|
||||||
|
checkIPAllowlist
|
||||||
});
|
});
|
||||||
|
|
||||||
if (membership) {
|
if (membership) {
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
import {
|
||||||
|
ALGORITHM_AES_256_GCM,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
} from "../variables";
|
||||||
|
|
||||||
|
export interface IBotOrg {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
publicKey: string;
|
||||||
|
encryptedSymmetricKey: string;
|
||||||
|
symmetricKeyIV: string;
|
||||||
|
symmetricKeyTag: string;
|
||||||
|
symmetricKeyAlgorithm: "aes-256-gcm";
|
||||||
|
symmetricKeyKeyEncoding: "base64" | "utf8";
|
||||||
|
encryptedPrivateKey: string;
|
||||||
|
privateKeyIV: string;
|
||||||
|
privateKeyTag: string;
|
||||||
|
privateKeyAlgorithm: "aes-256-gcm";
|
||||||
|
privateKeyKeyEncoding: "base64" | "utf8";
|
||||||
|
}
|
||||||
|
|
||||||
|
const botOrgSchema = new Schema<IBotOrg>(
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
publicKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
encryptedSymmetricKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
symmetricKeyIV: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
symmetricKeyTag: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
symmetricKeyAlgorithm: {
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
symmetricKeyKeyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
encryptedPrivateKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
privateKeyIV: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
privateKeyTag: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
privateKeyAlgorithm: {
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
privateKeyKeyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
ENCODING_SCHEME_UTF8,
|
||||||
|
ENCODING_SCHEME_BASE64,
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
|
||||||
|
|
||||||
|
export default BotOrg;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
type GitAppInstallationSession = {
|
||||||
|
id: string;
|
||||||
|
sessionId: string;
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
user: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitAppInstallationSession = new Schema<GitAppInstallationSession>({
|
||||||
|
id: {
|
||||||
|
required: true,
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
sessionId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const GitAppInstallationSession = model<GitAppInstallationSession>("git_app_installation_session", gitAppInstallationSession);
|
||||||
|
|
||||||
|
export default GitAppInstallationSession;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
type Installation = {
|
||||||
|
installationId: string
|
||||||
|
organizationId: string
|
||||||
|
user: Schema.Types.ObjectId
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gitAppOrganizationInstallation = new Schema<Installation>({
|
||||||
|
installationId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
organizationId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);
|
||||||
|
|
||||||
|
export default GitAppOrganizationInstallation;
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { Schema, model } from "mongoose";
|
||||||
|
|
||||||
|
export const STATUS_RESOLVED_FALSE_POSITIVE = "RESOLVED_FALSE_POSITIVE";
|
||||||
|
export const STATUS_RESOLVED_REVOKED = "RESOLVED_REVOKED";
|
||||||
|
export const STATUS_RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED";
|
||||||
|
export const STATUS_UNRESOLVED = "UNRESOLVED";
|
||||||
|
|
||||||
|
export type GitRisks = {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
startLine: string;
|
||||||
|
endLine: string;
|
||||||
|
startColumn: string;
|
||||||
|
endColumn: string;
|
||||||
|
match: string;
|
||||||
|
secret: string;
|
||||||
|
file: string;
|
||||||
|
symlinkFile: string;
|
||||||
|
commit: string;
|
||||||
|
entropy: string;
|
||||||
|
author: string;
|
||||||
|
email: string;
|
||||||
|
date: string;
|
||||||
|
message: string;
|
||||||
|
tags: string[];
|
||||||
|
ruleID: string;
|
||||||
|
fingerprint: string;
|
||||||
|
fingerPrintWithoutCommitId: string
|
||||||
|
|
||||||
|
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||||
|
isResolved: boolean; // New field for marking risks as resolved
|
||||||
|
riskOwner: string | null; // New field for setting a risk owner (nullable string)
|
||||||
|
installationId: string,
|
||||||
|
repositoryId: string,
|
||||||
|
repositoryLink: string
|
||||||
|
repositoryFullName: string
|
||||||
|
status: string
|
||||||
|
pusher: {
|
||||||
|
name: string,
|
||||||
|
email: string
|
||||||
|
},
|
||||||
|
organization: Schema.Types.ObjectId,
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitRisks = new Schema<GitRisks>({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
startLine: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
endLine: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
startColumn: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
endColumn: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
symlinkFile: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
commit: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
entropy: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: [String],
|
||||||
|
},
|
||||||
|
ruleID: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
fingerprint: {
|
||||||
|
type: String,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
fingerPrintWithoutCommitId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
isFalsePositive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isResolved: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
riskOwner: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
installationId: {
|
||||||
|
type: String,
|
||||||
|
require: true
|
||||||
|
},
|
||||||
|
repositoryId: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
repositoryLink: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
repositoryFullName: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
pusher: {
|
||||||
|
name: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
STATUS_RESOLVED_FALSE_POSITIVE,
|
||||||
|
STATUS_RESOLVED_REVOKED,
|
||||||
|
STATUS_RESOLVED_NOT_REVOKED,
|
||||||
|
STATUS_UNRESOLVED
|
||||||
|
],
|
||||||
|
default: STATUS_UNRESOLVED
|
||||||
|
}
|
||||||
|
}, { timestamps: true });
|
||||||
|
|
||||||
|
const GitRisks = model<GitRisks>("GitRisks", gitRisks);
|
||||||
|
|
||||||
|
export default GitRisks;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import BackupPrivateKey, { IBackupPrivateKey } from "./backupPrivateKey";
|
import BackupPrivateKey, { IBackupPrivateKey } from "./backupPrivateKey";
|
||||||
import Bot, { IBot } from "./bot";
|
import Bot, { IBot } from "./bot";
|
||||||
|
import BotOrg, { IBotOrg } from "./botOrg";
|
||||||
import BotKey, { IBotKey } from "./botKey";
|
import BotKey, { IBotKey } from "./botKey";
|
||||||
import IncidentContactOrg, { IIncidentContactOrg } from "./incidentContactOrg";
|
import IncidentContactOrg, { IIncidentContactOrg } from "./incidentContactOrg";
|
||||||
import Integration, { IIntegration } from "./integration";
|
import Integration, { IIntegration } from "./integration";
|
||||||
@@ -16,13 +17,14 @@ import ServiceAccountKey, { IServiceAccountKey } from "./serviceAccountKey"; //
|
|||||||
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
|
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
|
||||||
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
|
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
|
||||||
import TokenData, { ITokenData } from "./tokenData";
|
import TokenData, { ITokenData } from "./tokenData";
|
||||||
import User,{ AuthProvider, IUser } from "./user";
|
import User, { AuthProvider, IUser } from "./user";
|
||||||
import UserAction, { IUserAction } from "./userAction";
|
import UserAction, { IUserAction } from "./userAction";
|
||||||
import Workspace, { IWorkspace } from "./workspace";
|
import Workspace, { IWorkspace } from "./workspace";
|
||||||
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
||||||
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
||||||
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
||||||
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
||||||
|
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "./gitRisks";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AuthProvider,
|
AuthProvider,
|
||||||
@@ -30,6 +32,8 @@ export {
|
|||||||
IBackupPrivateKey,
|
IBackupPrivateKey,
|
||||||
Bot,
|
Bot,
|
||||||
IBot,
|
IBot,
|
||||||
|
BotOrg,
|
||||||
|
IBotOrg,
|
||||||
BotKey,
|
BotKey,
|
||||||
IBotKey,
|
IBotKey,
|
||||||
IncidentContactOrg,
|
IncidentContactOrg,
|
||||||
@@ -76,4 +80,6 @@ export {
|
|||||||
ILoginSRPDetail,
|
ILoginSRPDetail,
|
||||||
TokenVersion,
|
TokenVersion,
|
||||||
ITokenVersion,
|
ITokenVersion,
|
||||||
|
GitRisks,
|
||||||
|
STATUS_RESOLVED_FALSE_POSITIVE
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { Schema, Types, model } from "mongoose";
|
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
@@ -18,8 +21,9 @@ import {
|
|||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_TERRAFORM_CLOUD,
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
export interface IIntegration {
|
export interface IIntegration {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
@@ -56,7 +60,11 @@ export interface IIntegration {
|
|||||||
| "checkly"
|
| "checkly"
|
||||||
| "terraform-cloud"
|
| "terraform-cloud"
|
||||||
| "hashicorp-vault"
|
| "hashicorp-vault"
|
||||||
| "cloudflare-pages";
|
| "cloudflare-pages"
|
||||||
|
| "bitbucket"
|
||||||
|
| "codefresh"
|
||||||
|
| "digital-ocean-app-platform"
|
||||||
|
| "cloud-66"
|
||||||
integrationAuth: Types.ObjectId;
|
integrationAuth: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +155,10 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
INTEGRATION_TERRAFORM_CLOUD,
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
|
||||||
import {
|
import {
|
||||||
ALGORITHM_AES_256_GCM,
|
ALGORITHM_AES_256_GCM,
|
||||||
ENCODING_SCHEME_BASE64,
|
ENCODING_SCHEME_BASE64,
|
||||||
@@ -6,8 +5,12 @@ import {
|
|||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
INTEGRATION_AWS_SECRET_MANAGER,
|
INTEGRATION_AWS_SECRET_MANAGER,
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
@@ -22,11 +25,34 @@ import {
|
|||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
INTEGRATION_VERCEL
|
INTEGRATION_VERCEL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
export interface IIntegrationAuth extends Document {
|
export interface IIntegrationAuth extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
integration: "heroku" | "vercel" | "netlify" | "github" | "gitlab" | "render" | "railway" | "flyio" | "azure-key-vault" | "laravel-forge" | "circleci" | "travisci" | "supabase" | "aws-parameter-store" | "aws-secret-manager" | "terraform-cloud" | "checkly" | "cloudflare-pages";
|
integration:
|
||||||
|
| "heroku"
|
||||||
|
| "vercel"
|
||||||
|
| "netlify"
|
||||||
|
| "github"
|
||||||
|
| "gitlab"
|
||||||
|
| "render"
|
||||||
|
| "railway"
|
||||||
|
| "flyio"
|
||||||
|
| "azure-key-vault"
|
||||||
|
| "laravel-forge"
|
||||||
|
| "circleci"
|
||||||
|
| "travisci"
|
||||||
|
| "supabase"
|
||||||
|
| "aws-parameter-store"
|
||||||
|
| "aws-secret-manager"
|
||||||
|
| "checkly"
|
||||||
|
| "cloudflare-pages"
|
||||||
|
| "codefresh"
|
||||||
|
| "digital-ocean-app-platform"
|
||||||
|
| "bitbucket"
|
||||||
|
| "cloud-66"
|
||||||
|
| "terraform-cloud";
|
||||||
teamId: string;
|
teamId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -73,6 +99,10 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
|||||||
INTEGRATION_TERRAFORM_CLOUD,
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
|
INTEGRATION_BITBUCKET,
|
||||||
|
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||||
|
INTEGRATION_CODEFRESH,
|
||||||
|
INTEGRATION_CLOUD_66,
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface ISecretImports {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
folderId: string;
|
||||||
|
imports: Array<{
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretImportSchema = new Schema<ISecretImports>(
|
||||||
|
{
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
folderId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: "root"
|
||||||
|
},
|
||||||
|
imports: {
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretPath: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema);
|
||||||
|
export default SecretImport;
|
||||||
@@ -4,7 +4,10 @@ export interface IServiceTokenData extends Document {
|
|||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
name: string;
|
name: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string;
|
scopes: Array<{
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
}>;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
serviceAccount: Types.ObjectId;
|
serviceAccount: Types.ObjectId;
|
||||||
lastUsed: Date;
|
lastUsed: Date;
|
||||||
@@ -13,7 +16,6 @@ export interface IServiceTokenData extends Document {
|
|||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
secretPath: string;
|
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,68 +23,72 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
|||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "Workspace",
|
ref: "Workspace",
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
environment: {
|
scopes: {
|
||||||
type: String,
|
type: [
|
||||||
required: true,
|
{
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretPath: {
|
||||||
|
type: String,
|
||||||
|
default: "/",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: true
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "User",
|
ref: "User",
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
serviceAccount: {
|
serviceAccount: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "ServiceAccount",
|
ref: "ServiceAccount"
|
||||||
},
|
},
|
||||||
lastUsed: {
|
lastUsed: {
|
||||||
type: Date,
|
type: Date
|
||||||
},
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
type: Date,
|
type: Date
|
||||||
},
|
},
|
||||||
secretHash: {
|
secretHash: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
select: false,
|
select: false
|
||||||
},
|
},
|
||||||
encryptedKey: {
|
encryptedKey: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false,
|
select: false
|
||||||
},
|
},
|
||||||
iv: {
|
iv: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false,
|
select: false
|
||||||
},
|
},
|
||||||
tag: {
|
tag: {
|
||||||
type: String,
|
type: String,
|
||||||
select: false,
|
select: false
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
type: [String],
|
type: [String],
|
||||||
enum: ["read", "write"],
|
enum: ["read", "write"],
|
||||||
default: ["read"],
|
default: ["read"]
|
||||||
},
|
}
|
||||||
secretPath: {
|
|
||||||
type: String,
|
|
||||||
default: "/",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ServiceTokenData = model<IServiceTokenData>(
|
const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);
|
||||||
"ServiceTokenData",
|
|
||||||
serviceTokenDataSchema
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ServiceTokenData;
|
export default ServiceTokenData;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
export enum AuthProvider {
|
export enum AuthProvider {
|
||||||
|
EMAIL = "email",
|
||||||
GOOGLE = "google",
|
GOOGLE = "google",
|
||||||
|
OKTA_SAML = "okta-saml"
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUser extends Document {
|
export interface IUser extends Document {
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables";
|
||||||
|
|
||||||
|
export interface IWebhook extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
url: string;
|
||||||
|
lastStatus: "success" | "failed";
|
||||||
|
lastRunErrorMessage?: string;
|
||||||
|
isDisabled: boolean;
|
||||||
|
encryptedSecretKey: string;
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
algorithm: "aes-256-gcm";
|
||||||
|
keyEncoding: "base64" | "utf8";
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebhookSchema = new Schema<IWebhook>(
|
||||||
|
{
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: "/"
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
lastStatus: {
|
||||||
|
type: String,
|
||||||
|
enum: ["success", "failed"]
|
||||||
|
},
|
||||||
|
lastRunErrorMessage: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// used for webhook signature
|
||||||
|
encryptedSecretKey: {
|
||||||
|
type: String,
|
||||||
|
select: false
|
||||||
|
},
|
||||||
|
iv: {
|
||||||
|
type: String,
|
||||||
|
select: false
|
||||||
|
},
|
||||||
|
tag: {
|
||||||
|
type: String,
|
||||||
|
select: false
|
||||||
|
},
|
||||||
|
algorithm: {
|
||||||
|
// the encryption algorithm used
|
||||||
|
type: String,
|
||||||
|
enum: [ALGORITHM_AES_256_GCM],
|
||||||
|
required: true,
|
||||||
|
select: false
|
||||||
|
},
|
||||||
|
keyEncoding: {
|
||||||
|
type: String,
|
||||||
|
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||||
|
required: true,
|
||||||
|
select: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Webhook = model<IWebhook>("Webhook", WebhookSchema);
|
||||||
|
|
||||||
|
export default Webhook;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import express, { Request, Response } from "express";
|
import express, { Request, Response } from "express";
|
||||||
import { getSmtpConfigured } from "../../config";
|
import { getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookSecret, getSmtpConfigured } from "../../config";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ router.get(
|
|||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: "Ok",
|
message: "Ok",
|
||||||
emailConfigured: await getSmtpConfigured(),
|
emailConfigured: await getSmtpConfigured(),
|
||||||
|
secretScanningConfigured: await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from "express-validator";
|
import { body } from "express-validator";
|
||||||
import passport from "passport";
|
|
||||||
import { requireAuth, validateRequest } from "../../middleware";
|
import { requireAuth, validateRequest } from "../../middleware";
|
||||||
import { authController } from "../../controllers/v1";
|
import { authController } from "../../controllers/v1";
|
||||||
import { authLimiter } from "../../helpers/rateLimiter";
|
import { authLimiter } from "../../helpers/rateLimiter";
|
||||||
@@ -44,21 +43,6 @@ router.post(
|
|||||||
authController.checkAuth
|
authController.checkAuth
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/redirect/google",
|
|
||||||
authLimiter,
|
|
||||||
passport.authenticate("google", {
|
|
||||||
scope: ["profile", "email"],
|
|
||||||
session: false,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/callback/google",
|
|
||||||
passport.authenticate("google", { failureRedirect: "/login/provider/error", session: false }),
|
|
||||||
authController.handleAuthProviderCallback,
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/common-passwords",
|
"/common-passwords",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
|
|||||||
@@ -15,23 +15,29 @@ import password from "./password";
|
|||||||
import integration from "./integration";
|
import integration from "./integration";
|
||||||
import integrationAuth from "./integrationAuth";
|
import integrationAuth from "./integrationAuth";
|
||||||
import secretsFolder from "./secretsFolder";
|
import secretsFolder from "./secretsFolder";
|
||||||
|
import secretScanning from "./secretScanning";
|
||||||
|
import webhooks from "./webhook";
|
||||||
|
import secretImport from "./secretImport";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
signup,
|
signup,
|
||||||
auth,
|
auth,
|
||||||
bot,
|
bot,
|
||||||
user,
|
user,
|
||||||
userAction,
|
userAction,
|
||||||
organization,
|
organization,
|
||||||
workspace,
|
workspace,
|
||||||
membershipOrg,
|
membershipOrg,
|
||||||
membership,
|
membership,
|
||||||
key,
|
key,
|
||||||
inviteOrg,
|
inviteOrg,
|
||||||
secret,
|
secret,
|
||||||
serviceToken,
|
serviceToken,
|
||||||
password,
|
password,
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secretsFolder,
|
secretsFolder,
|
||||||
|
secretScanning,
|
||||||
|
webhooks,
|
||||||
|
secretImport
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
param("integrationAuthId"),
|
param("integrationAuthId"),
|
||||||
query("teamId"),
|
query("teamId"),
|
||||||
|
query("workspaceSlug"),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
integrationAuthController.getIntegrationAuthApps
|
integrationAuthController.getIntegrationAuthApps
|
||||||
);
|
);
|
||||||
@@ -141,6 +142,19 @@ router.get(
|
|||||||
integrationAuthController.getIntegrationAuthRailwayServices
|
integrationAuthController.getIntegrationAuthRailwayServices
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:integrationAuthId/bitbucket/workspaces",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
}),
|
||||||
|
requireIntegrationAuthorizationAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
}),
|
||||||
|
param("integrationAuthId").exists().isString(),
|
||||||
|
validateRequest,
|
||||||
|
integrationAuthController.getIntegrationAuthBitBucketWorkspaces
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:integrationAuthId",
|
"/:integrationAuthId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { body, param, query } from "express-validator";
|
||||||
|
import { secretImportController } from "../../controllers/v1";
|
||||||
|
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
|
||||||
|
import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "body"
|
||||||
|
}),
|
||||||
|
body("workspaceId").exists().isString().trim().notEmpty(),
|
||||||
|
body("environment").exists().isString().trim().notEmpty(),
|
||||||
|
body("folderId").default("root").isString().trim(),
|
||||||
|
body("secretImport").exists().isObject(),
|
||||||
|
body("secretImport.environment").isString().exists().trim(),
|
||||||
|
body("secretImport.secretPath").isString().exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
secretImportController.createSecretImport
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
"/:id",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
param("id").exists().isString().trim(),
|
||||||
|
body("secretImports").exists().isArray(),
|
||||||
|
body("secretImports.*.environment").isString().exists().trim(),
|
||||||
|
body("secretImports.*.secretPath").isString().exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
secretImportController.updateSecretImport
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:id",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
param("id").exists().isString().trim(),
|
||||||
|
body("secretImportPath").isString().exists().trim(),
|
||||||
|
body("secretImportEnv").isString().exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
secretImportController.deleteSecretImport
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "query"
|
||||||
|
}),
|
||||||
|
query("workspaceId").exists().isString().trim().notEmpty(),
|
||||||
|
query("environment").exists().isString().trim().notEmpty(),
|
||||||
|
query("folderId").default("root").isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
secretImportController.getSecretImports
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/secrets",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "query"
|
||||||
|
}),
|
||||||
|
query("workspaceId").exists().isString().trim().notEmpty(),
|
||||||
|
query("environment").exists().isString().trim().notEmpty(),
|
||||||
|
query("folderId").default("root").isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
secretImportController.getAllSecretsFromImport
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireOrganizationAuth,
|
||||||
|
validateRequest,
|
||||||
|
} from "../../middleware";
|
||||||
|
import { body, param } from "express-validator";
|
||||||
|
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController";
|
||||||
|
import { ACCEPTED, ADMIN, MEMBER, OWNER } from "../../variables";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/create-installation-session/organization/:organizationId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
createInstallationSession
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/link-installation",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
body("installationId").exists().trim(),
|
||||||
|
body("sessionId").exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
linkInstallationToOrganization
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/installation-status/organization/:organizationId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
getCurrentOrganizationInstallationStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/organization/:organizationId/risks",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
getRisksForOrganization
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/organization/:organizationId/risks/:riskId/status",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ["jwt"],
|
||||||
|
}),
|
||||||
|
param("organizationId").exists().trim(),
|
||||||
|
param("riskId").exists().trim(),
|
||||||
|
body("status").exists(),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
}),
|
||||||
|
validateRequest,
|
||||||
|
updateRisksStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
|
||||||
|
import { body, param, query } from "express-validator";
|
||||||
|
import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables";
|
||||||
|
import { webhookController } from "../../controllers/v1";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "body",
|
||||||
|
locationEnvironment: "body"
|
||||||
|
}),
|
||||||
|
body("workspaceId").exists().isString().trim(),
|
||||||
|
body("environment").exists().isString().trim(),
|
||||||
|
body("webhookUrl").exists().isString().isURL().trim(),
|
||||||
|
body("webhookSecretKey").isString().trim(),
|
||||||
|
body("secretPath").default("/").isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
webhookController.createWebhook
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:webhookId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
param("webhookId").exists().isString().trim(),
|
||||||
|
body("isDisabled").default(false).isBoolean(),
|
||||||
|
validateRequest,
|
||||||
|
webhookController.updateWebhook
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:webhookId/test",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
param("webhookId").exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
webhookController.testWebhook
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:webhookId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
param("webhookId").exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
webhookController.deleteWebhook
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: "query",
|
||||||
|
locationEnvironment: "query"
|
||||||
|
}),
|
||||||
|
query("workspaceId").exists().isString().trim(),
|
||||||
|
query("environment").optional().isString().trim(),
|
||||||
|
query("secretPath").optional().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
webhookController.listWebhooks
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireSecretsAuth,
|
requireSecretsAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest,
|
validateRequest
|
||||||
} from "../../middleware";
|
} from "../../middleware";
|
||||||
import { validateClientForSecrets } from "../../validation";
|
import { validateClientForSecrets } from "../../validation";
|
||||||
import { body, query } from "express-validator";
|
import { body, query } from "express-validator";
|
||||||
@@ -20,22 +20,18 @@ import {
|
|||||||
PERMISSION_READ_SECRETS,
|
PERMISSION_READ_SECRETS,
|
||||||
PERMISSION_WRITE_SECRETS,
|
PERMISSION_WRITE_SECRETS,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { BatchSecretRequest } from "../../types/secret";
|
import { BatchSecretRequest } from "../../types/secret";
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/batch",
|
"/batch",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: "body",
|
locationWorkspaceId: "body"
|
||||||
}),
|
}),
|
||||||
body("workspaceId").exists().isString().trim(),
|
body("workspaceId").exists().isString().trim(),
|
||||||
body("folderId").default("root").isString().trim(),
|
body("folderId").default("root").isString().trim(),
|
||||||
@@ -52,10 +48,8 @@ router.post(
|
|||||||
if (secretIds.length > 0) {
|
if (secretIds.length > 0) {
|
||||||
req.secrets = await validateClientForSecrets({
|
req.secrets = await validateClientForSecrets({
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
secretIds: secretIds.map(
|
secretIds: secretIds.map((secretId: string) => new Types.ObjectId(secretId)),
|
||||||
(secretId: string) => new Types.ObjectId(secretId)
|
requiredPermissions: []
|
||||||
),
|
|
||||||
requiredPermissions: [],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,14 +70,11 @@ router.post(
|
|||||||
.custom((value) => {
|
.custom((value) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// case: create multiple secrets
|
// case: create multiple secrets
|
||||||
if (value.length === 0)
|
if (value.length === 0) throw new Error("secrets cannot be an empty array");
|
||||||
throw new Error("secrets cannot be an empty array");
|
|
||||||
for (const secret of value) {
|
for (const secret of value) {
|
||||||
if (
|
if (
|
||||||
!secret.type ||
|
!secret.type ||
|
||||||
!(
|
!(secret.type === SECRET_PERSONAL || secret.type === SECRET_SHARED) ||
|
||||||
secret.type === SECRET_PERSONAL || secret.type === SECRET_SHARED
|
|
||||||
) ||
|
|
||||||
!secret.secretKeyCiphertext ||
|
!secret.secretKeyCiphertext ||
|
||||||
!secret.secretKeyIV ||
|
!secret.secretKeyIV ||
|
||||||
!secret.secretKeyTag ||
|
!secret.secretKeyTag ||
|
||||||
@@ -108,9 +99,7 @@ router.post(
|
|||||||
!value.secretValueIV ||
|
!value.secretValueIV ||
|
||||||
!value.secretValueTag
|
!value.secretValueTag
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error("secrets object is missing required secret properties");
|
||||||
"secrets object is missing required secret properties"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("secrets must be an object or an array of objects");
|
throw new Error("secrets must be an object or an array of objects");
|
||||||
@@ -120,17 +109,13 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: "body",
|
locationWorkspaceId: "body",
|
||||||
locationEnvironment: "body",
|
locationEnvironment: "body",
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.createSecrets
|
secretsController.createSecrets
|
||||||
);
|
);
|
||||||
@@ -142,20 +127,21 @@ router.get(
|
|||||||
query("tagSlugs"),
|
query("tagSlugs"),
|
||||||
query("folderId").default("root").isString().trim(),
|
query("folderId").default("root").isString().trim(),
|
||||||
query("secretPath").optional().isString().trim(),
|
query("secretPath").optional().isString().trim(),
|
||||||
|
query("include_imports").optional().default(false).isBoolean(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [
|
acceptedAuthModes: [
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: "query",
|
locationWorkspaceId: "query",
|
||||||
locationEnvironment: "query",
|
locationEnvironment: "query",
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
@@ -167,8 +153,7 @@ router.patch(
|
|||||||
.custom((value) => {
|
.custom((value) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// case: update multiple secrets
|
// case: update multiple secrets
|
||||||
if (value.length === 0)
|
if (value.length === 0) throw new Error("secrets cannot be an empty array");
|
||||||
throw new Error("secrets cannot be an empty array");
|
|
||||||
for (const secret of value) {
|
for (const secret of value) {
|
||||||
if (!secret.id) {
|
if (!secret.id) {
|
||||||
throw new Error("Each secret must contain a ID property");
|
throw new Error("Each secret must contain a ID property");
|
||||||
@@ -187,15 +172,11 @@ router.patch(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecrets
|
secretsController.updateSecrets
|
||||||
);
|
);
|
||||||
@@ -210,8 +191,7 @@ router.delete(
|
|||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// case: delete multiple secrets
|
// case: delete multiple secrets
|
||||||
if (value.length === 0)
|
if (value.length === 0) throw new Error("secrets cannot be an empty array");
|
||||||
throw new Error("secrets cannot be an empty array");
|
|
||||||
return value.every((id: string) => typeof id === "string");
|
return value.every((id: string) => typeof id === "string");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,15 +201,11 @@ router.delete(
|
|||||||
.isEmpty(),
|
.isEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
AUTH_MODE_JWT,
|
|
||||||
AUTH_MODE_API_KEY,
|
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecrets
|
secretsController.deleteSecrets
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireServiceTokenDataAuth,
|
requireServiceTokenDataAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest,
|
validateRequest
|
||||||
} from "../../middleware";
|
} from "../../middleware";
|
||||||
import { body, param } from "express-validator";
|
import { body, param } from "express-validator";
|
||||||
import {
|
import {
|
||||||
@@ -13,14 +13,14 @@ import {
|
|||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
PERMISSION_WRITE_SECRETS,
|
PERMISSION_WRITE_SECRETS
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
import { serviceTokenDataController } from "../../controllers/v2";
|
import { serviceTokenDataController } from "../../controllers/v2";
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN],
|
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
serviceTokenDataController.getServiceTokenData
|
serviceTokenDataController.getServiceTokenData
|
||||||
);
|
);
|
||||||
@@ -28,33 +28,30 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
locationWorkspaceId: "body",
|
locationWorkspaceId: "body",
|
||||||
locationEnvironment: "body",
|
locationEnvironment: "body",
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
body("name").exists().isString().trim(),
|
body("name").exists().isString().trim(),
|
||||||
body("workspaceId").exists().isString().trim(),
|
body("workspaceId").exists().isString().trim(),
|
||||||
body("environment").exists().isString().trim(),
|
body("scopes").exists().isArray(),
|
||||||
|
body("scopes.*.environment").exists().isString().trim(),
|
||||||
|
body("scopes.*.secretPath").exists().isString().trim(),
|
||||||
body("encryptedKey").exists().isString().trim(),
|
body("encryptedKey").exists().isString().trim(),
|
||||||
body("iv").exists().isString().trim(),
|
body("iv").exists().isString().trim(),
|
||||||
body("secretPath").isString().default("/").trim(),
|
|
||||||
body("tag").exists().isString().trim(),
|
body("tag").exists().isString().trim(),
|
||||||
body("expiresIn").exists().isNumeric(), // measured in ms
|
body("expiresIn").exists().isNumeric(), // measured in ms
|
||||||
body("permissions")
|
body("permissions")
|
||||||
.isArray({ min: 1 })
|
.isArray({ min: 1 })
|
||||||
.custom((value: string[]) => {
|
.custom((value: string[]) => {
|
||||||
const allowedPermissions = ["read", "write"];
|
const allowedPermissions = ["read", "write"];
|
||||||
const invalidValues = value.filter(
|
const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
|
||||||
(v) => !allowedPermissions.includes(v)
|
|
||||||
);
|
|
||||||
if (invalidValues.length > 0) {
|
if (invalidValues.length > 0) {
|
||||||
throw new Error(
|
throw new Error(`permissions contains invalid values: ${invalidValues.join(", ")}`);
|
||||||
`permissions contains invalid values: ${invalidValues.join(", ")}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -66,10 +63,10 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:serviceTokenDataId",
|
"/:serviceTokenDataId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireServiceTokenDataAuth({
|
requireServiceTokenDataAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
}),
|
}),
|
||||||
param("serviceTokenDataId").exists().trim(),
|
param("serviceTokenDataId").exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import {
|
|||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
|
import {
|
||||||
|
AuthProvider
|
||||||
|
} from "../../models";
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/me",
|
"/me",
|
||||||
@@ -29,6 +32,30 @@ router.patch(
|
|||||||
usersController.updateMyMfaEnabled
|
usersController.updateMyMfaEnabled
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/me/name",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||||
|
}),
|
||||||
|
body("firstName").exists().isString(),
|
||||||
|
body("lastName").isString(),
|
||||||
|
validateRequest,
|
||||||
|
usersController.updateName
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/me/auth-provider",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||||
|
}),
|
||||||
|
body("authProvider").exists().isString().isIn([
|
||||||
|
AuthProvider.EMAIL,
|
||||||
|
AuthProvider.GOOGLE
|
||||||
|
]),
|
||||||
|
validateRequest,
|
||||||
|
usersController.updateAuthProvider
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/me/organizations",
|
"/me/organizations",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
@@ -66,7 +93,7 @@ router.delete(
|
|||||||
usersController.deleteAPIKey
|
usersController.deleteAPIKey
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get( // new
|
router.get(
|
||||||
"/me/sessions",
|
"/me/sessions",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
@@ -74,7 +101,7 @@ router.get( // new
|
|||||||
usersController.getMySessions
|
usersController.getMySessions
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete( // new
|
router.delete(
|
||||||
"/me/sessions",
|
"/me/sessions",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
|
||||||
requireAuth,
|
|
||||||
requireWorkspaceAuth,
|
|
||||||
validateRequest,
|
|
||||||
} from "../../middleware";
|
|
||||||
import { body, param, query } from "express-validator";
|
import { body, param, query } from "express-validator";
|
||||||
import { secretsController } from "../../controllers/v3";
|
import { secretsController } from "../../controllers/v3";
|
||||||
import {
|
import {
|
||||||
@@ -17,30 +13,23 @@ import {
|
|||||||
PERMISSION_READ_SECRETS,
|
PERMISSION_READ_SECRETS,
|
||||||
PERMISSION_WRITE_SECRETS,
|
PERMISSION_WRITE_SECRETS,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED
|
||||||
} from "../../variables";
|
} from "../../variables";
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/raw",
|
"/raw",
|
||||||
query("workspaceId").exists().isString().trim(),
|
query("workspaceId").optional().isString().trim(),
|
||||||
query("environment").exists().isString().trim(),
|
query("environment").optional().isString().trim(),
|
||||||
query("secretPath").default("/").isString().trim(),
|
query("secretPath").default("/").isString().trim(),
|
||||||
|
query("include_imports").optional().isBoolean().default(false),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [
|
acceptedAuthModes: [
|
||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
|
||||||
requireWorkspaceAuth({
|
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
|
||||||
locationWorkspaceId: "query",
|
|
||||||
locationEnvironment: "query",
|
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
|
||||||
requireBlindIndicesEnabled: true,
|
|
||||||
requireE2EEOff: true,
|
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretsRaw
|
secretsController.getSecretsRaw
|
||||||
);
|
);
|
||||||
@@ -58,8 +47,8 @@ router.get(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -68,6 +57,7 @@ router.get(
|
|||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretByNameRaw
|
secretsController.getSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -86,8 +76,8 @@ router.post(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -96,6 +86,7 @@ router.post(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.createSecretRaw
|
secretsController.createSecretRaw
|
||||||
);
|
);
|
||||||
@@ -114,8 +105,8 @@ router.patch(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -124,6 +115,7 @@ router.patch(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecretByNameRaw
|
secretsController.updateSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -141,8 +133,8 @@ router.delete(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -151,6 +143,7 @@ router.delete(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: true,
|
requireE2EEOff: true,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecretByNameRaw
|
secretsController.deleteSecretByNameRaw
|
||||||
);
|
);
|
||||||
@@ -166,8 +159,8 @@ router.get(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -176,6 +169,7 @@ router.get(
|
|||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
@@ -201,8 +195,8 @@ router.post(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -211,6 +205,7 @@ router.post(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.createSecret
|
secretsController.createSecret
|
||||||
);
|
);
|
||||||
@@ -228,8 +223,8 @@ router.get(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -237,6 +232,7 @@ router.get(
|
|||||||
locationEnvironment: "query",
|
locationEnvironment: "query",
|
||||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretByName
|
secretsController.getSecretByName
|
||||||
);
|
);
|
||||||
@@ -257,8 +253,8 @@ router.patch(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -267,6 +263,7 @@ router.patch(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecretByName
|
secretsController.updateSecretByName
|
||||||
);
|
);
|
||||||
@@ -284,8 +281,8 @@ router.delete(
|
|||||||
AUTH_MODE_JWT,
|
AUTH_MODE_JWT,
|
||||||
AUTH_MODE_API_KEY,
|
AUTH_MODE_API_KEY,
|
||||||
AUTH_MODE_SERVICE_TOKEN,
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
AUTH_MODE_SERVICE_ACCOUNT,
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -294,6 +291,7 @@ router.delete(
|
|||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||||
requireBlindIndicesEnabled: true,
|
requireBlindIndicesEnabled: true,
|
||||||
requireE2EEOff: false,
|
requireE2EEOff: false,
|
||||||
|
checkIPAllowlist: true
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecretByName
|
secretsController.deleteSecretByName
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ router.post(
|
|||||||
body("salt").exists().isString().trim().notEmpty(),
|
body("salt").exists().isString().trim().notEmpty(),
|
||||||
body("verifier").exists().isString().trim().notEmpty(),
|
body("verifier").exists().isString().trim().notEmpty(),
|
||||||
body("organizationName").exists().isString().trim().notEmpty(),
|
body("organizationName").exists().isString().trim().notEmpty(),
|
||||||
body("providerAuthToken").isString().trim().optional({nullable: true}),
|
body("providerAuthToken").isString().trim().optional({ nullable: true }),
|
||||||
|
body("attributionSource").optional().isString().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
signupController.completeAccountSignup,
|
signupController.completeAccountSignup,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import { getSymmetricKeyHelper } from "../helpers/botOrg";
|
||||||
|
|
||||||
|
// TODO: DOCstrings
|
||||||
|
|
||||||
|
class BotOrgService {
|
||||||
|
static async getSymmetricKey(organizationId: Types.ObjectId) {
|
||||||
|
return await getSymmetricKeyHelper(organizationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BotOrgService;
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import { Probot } from "probot";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
import { mkdir, readFile, rm, writeFile } from "fs";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
import { join } from "path"
|
||||||
|
import GitRisks from "../models/gitRisks";
|
||||||
|
import GitAppOrganizationInstallation from "../models/gitAppOrganizationInstallation";
|
||||||
|
import MembershipOrg from "../models/membershipOrg";
|
||||||
|
import { ADMIN, OWNER } from "../variables";
|
||||||
|
import User from "../models/user";
|
||||||
|
import { sendMail } from "../helpers";
|
||||||
|
import TelemetryService from "./TelemetryService";
|
||||||
|
|
||||||
|
type SecretMatch = {
|
||||||
|
Description: string;
|
||||||
|
StartLine: number;
|
||||||
|
EndLine: number;
|
||||||
|
StartColumn: number;
|
||||||
|
EndColumn: number;
|
||||||
|
Match: string;
|
||||||
|
Secret: string;
|
||||||
|
File: string;
|
||||||
|
SymlinkFile: string;
|
||||||
|
Commit: string;
|
||||||
|
Entropy: number;
|
||||||
|
Author: string;
|
||||||
|
Email: string;
|
||||||
|
Date: string;
|
||||||
|
Message: string;
|
||||||
|
Tags: string[];
|
||||||
|
RuleID: string;
|
||||||
|
Fingerprint: string;
|
||||||
|
FingerPrintWithoutCommitId: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (app: Probot) => {
|
||||||
|
app.on("installation.deleted", async (context) => {
|
||||||
|
const { payload } = context;
|
||||||
|
const { installation, repositories } = payload;
|
||||||
|
if (installation.repository_selection == "all") {
|
||||||
|
await GitRisks.deleteMany({ installationId: installation.id })
|
||||||
|
await GitAppOrganizationInstallation.deleteOne({ installationId: installation.id })
|
||||||
|
} else {
|
||||||
|
if (repositories) {
|
||||||
|
for (const repository of repositories) {
|
||||||
|
await GitRisks.deleteMany({ repositoryId: repository.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("push", async (context) => {
|
||||||
|
const { payload } = context;
|
||||||
|
const { commits, repository, installation, pusher } = payload;
|
||||||
|
const [owner, repo] = repository.full_name.split("/");
|
||||||
|
|
||||||
|
if (!commits || !repository || !installation || !pusher) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const installationLinkToOrgExists = await GitAppOrganizationInstallation.findOne({ installationId: installation?.id }).lean()
|
||||||
|
if (!installationLinkToOrgExists) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFindingsByFingerprint: { [key: string]: SecretMatch; } = {}
|
||||||
|
|
||||||
|
for (const commit of commits) {
|
||||||
|
for (const filepath of [...commit.added, ...commit.modified]) {
|
||||||
|
try {
|
||||||
|
const fileContentsResponse = await context.octokit.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: filepath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data: any = fileContentsResponse.data;
|
||||||
|
const fileContent = Buffer.from(data.content, "base64").toString();
|
||||||
|
|
||||||
|
const findings = await scanContentAndGetFindings(`\n${fileContent}`) // extra line to count lines correctly
|
||||||
|
|
||||||
|
for (const finding of findings) {
|
||||||
|
const fingerPrintWithCommitId = `${commit.id}:${filepath}:${finding.RuleID}:${finding.StartLine}`
|
||||||
|
const fingerPrintWithoutCommitId = `${filepath}:${finding.RuleID}:${finding.StartLine}`
|
||||||
|
finding.Fingerprint = fingerPrintWithCommitId
|
||||||
|
finding.FingerPrintWithoutCommitId = fingerPrintWithoutCommitId
|
||||||
|
finding.Commit = commit.id
|
||||||
|
finding.File = filepath
|
||||||
|
finding.Author = commit.author.name
|
||||||
|
finding.Email = commit?.author?.email ? commit?.author?.email : ""
|
||||||
|
|
||||||
|
allFindingsByFingerprint[fingerPrintWithCommitId] = finding
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching content for ${filepath}`, error); // eslint-disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change to update
|
||||||
|
for (const key in allFindingsByFingerprint) {
|
||||||
|
const risk = await GitRisks.findOneAndUpdate({ fingerprint: allFindingsByFingerprint[key].Fingerprint },
|
||||||
|
{
|
||||||
|
...convertKeysToLowercase(allFindingsByFingerprint[key]),
|
||||||
|
installationId: installation.id,
|
||||||
|
organization: installationLinkToOrgExists.organizationId,
|
||||||
|
repositoryFullName: repository.full_name,
|
||||||
|
repositoryId: repository.id
|
||||||
|
}, {
|
||||||
|
upsert: true
|
||||||
|
}).lean()
|
||||||
|
}
|
||||||
|
// get emails of admins
|
||||||
|
const adminsOfWork = await MembershipOrg.find({
|
||||||
|
organization: installationLinkToOrgExists.organizationId,
|
||||||
|
$or: [
|
||||||
|
{ role: OWNER },
|
||||||
|
{ role: ADMIN }
|
||||||
|
]
|
||||||
|
}).lean()
|
||||||
|
|
||||||
|
const userEmails = await User.find({
|
||||||
|
_id: {
|
||||||
|
$in: [adminsOfWork.map(orgMembership => orgMembership.user)]
|
||||||
|
}
|
||||||
|
}).select("email").lean()
|
||||||
|
|
||||||
|
const adminOrOwnerEmails = userEmails.map(userObject => userObject.email)
|
||||||
|
|
||||||
|
const usersToNotify = pusher?.email ? [pusher.email, ...adminOrOwnerEmails] : [...adminOrOwnerEmails]
|
||||||
|
if (Object.keys(allFindingsByFingerprint).length) {
|
||||||
|
await sendMail({
|
||||||
|
template: "secretLeakIncident.handlebars",
|
||||||
|
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.full_name}`,
|
||||||
|
recipients: usersToNotify,
|
||||||
|
substitutions: {
|
||||||
|
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
||||||
|
pusher_email: pusher.email,
|
||||||
|
pusher_name: pusher.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: "cloud secret scan",
|
||||||
|
distinctId: pusher.email,
|
||||||
|
properties: {
|
||||||
|
numberOfCommitsScanned: commits.length,
|
||||||
|
numberOfRisksFound: Object.keys(allFindingsByFingerprint).length,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function scanContentAndGetFindings(textContent: string): Promise<SecretMatch[]> {
|
||||||
|
const tempFolder = await createTempFolder();
|
||||||
|
const filePath = join(tempFolder, "content.txt");
|
||||||
|
const findingsPath = join(tempFolder, "findings.json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await writeTextToFile(filePath, textContent);
|
||||||
|
await runInfisicalScan(filePath, findingsPath);
|
||||||
|
const findingsData = await readFindingsFile(findingsPath);
|
||||||
|
return JSON.parse(findingsData);
|
||||||
|
} finally {
|
||||||
|
await deleteTempFolder(tempFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTempFolder(): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tempDir = tmpdir()
|
||||||
|
const tempFolderName = Math.random().toString(36).substring(2);
|
||||||
|
const tempFolderPath = join(tempDir, tempFolderName);
|
||||||
|
|
||||||
|
mkdir(tempFolderPath, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(tempFolderPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeTextToFile(filePath: string, content: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
writeFile(filePath, content, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runInfisicalScan(inputPath: string, outputPath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const command = `cat "${inputPath}" | infisical scan --exit-code=77 --pipe -r "${outputPath}"`;
|
||||||
|
exec(command, (error) => {
|
||||||
|
if (error && error.code != 77) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFindingsFile(filePath: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
readFile(filePath, "utf8", (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteTempFolder(folderPath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
rm(folderPath, { recursive: true }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertKeysToLowercase<T>(obj: T): T {
|
||||||
|
const convertedObj = {} as T;
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
const lowercaseKey = key.charAt(0).toLowerCase() + key.slice(1);
|
||||||
|
convertedObj[lowercaseKey as keyof T] = obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedObj;
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import Folder from "../models/folder";
|
||||||
|
import Secret, { ISecret } from "../models/secret";
|
||||||
|
import SecretImport from "../models/secretImports";
|
||||||
|
import { getFolderByPath } from "./FolderService";
|
||||||
|
|
||||||
|
type TSecretImportFid = { environment: string; folderId: string; secretPath: string };
|
||||||
|
|
||||||
|
export const getAllImportedSecrets = async (
|
||||||
|
workspaceId: string,
|
||||||
|
environment: string,
|
||||||
|
folderId = "root"
|
||||||
|
) => {
|
||||||
|
const secImports = await SecretImport.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
folderId
|
||||||
|
});
|
||||||
|
if (!secImports) return [];
|
||||||
|
if (secImports.imports.length === 0) return [];
|
||||||
|
|
||||||
|
const importedEnv: Record<string, boolean> = {}; // to get folders from all environment
|
||||||
|
secImports.imports.forEach((el) => (importedEnv[el.environment] = true));
|
||||||
|
|
||||||
|
const folders = await Folder.find({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: { $in: Object.keys(importedEnv) }
|
||||||
|
});
|
||||||
|
|
||||||
|
const importedSecByFid: TSecretImportFid[] = [];
|
||||||
|
secImports.imports.forEach((el) => {
|
||||||
|
const folder = folders.find((fl) => fl.environment === el.environment);
|
||||||
|
if (folder) {
|
||||||
|
const secPathFolder = getFolderByPath(folder.nodes, el.secretPath);
|
||||||
|
if (secPathFolder)
|
||||||
|
importedSecByFid.push({
|
||||||
|
environment: el.environment,
|
||||||
|
folderId: secPathFolder.id,
|
||||||
|
secretPath: el.secretPath
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (el.secretPath === "/") {
|
||||||
|
// this happens when importing with a fresh env without any folders
|
||||||
|
importedSecByFid.push({ environment: el.environment, folderId: "root", secretPath: "/" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (importedSecByFid.length === 0) return [];
|
||||||
|
|
||||||
|
const secsGroupedByRef = await Secret.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
type: "shared"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: {
|
||||||
|
environment: "$environment",
|
||||||
|
folderId: "$folder"
|
||||||
|
},
|
||||||
|
secrets: { $push: "$$ROOT" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
$or: importedSecByFid.map(({ environment, folderId: fid }) => ({
|
||||||
|
"_id.environment": environment,
|
||||||
|
"_id.folderId": fid
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// now let stitch together secrets.
|
||||||
|
const importedSecrets: Array<TSecretImportFid & { secrets: ISecret[] }> = [];
|
||||||
|
importedSecByFid.forEach(({ environment, folderId, secretPath }) => {
|
||||||
|
const secretsGrouped = secsGroupedByRef.find(
|
||||||
|
(el) => el._id.environment === environment && el._id.folderId === folderId
|
||||||
|
);
|
||||||
|
if (secretsGrouped) {
|
||||||
|
importedSecrets.push({ secretPath, folderId, environment, secrets: secretsGrouped.secrets });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return importedSecrets;
|
||||||
|
};
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import picomatch from "picomatch";
|
||||||
|
import { client, getRootEncryptionKey } from "../config";
|
||||||
|
import Webhook, { IWebhook } from "../models/webhooks";
|
||||||
|
|
||||||
|
export const triggerWebhookRequest = async (
|
||||||
|
{ url, encryptedSecretKey, iv, tag }: IWebhook,
|
||||||
|
payload: Record<string, unknown>
|
||||||
|
) => {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
payload["timestamp"] = Date.now();
|
||||||
|
|
||||||
|
if (encryptedSecretKey) {
|
||||||
|
const rootEncryptionKey = await getRootEncryptionKey();
|
||||||
|
const secretKey = client.decryptSymmetric(encryptedSecretKey, rootEncryptionKey, iv, tag);
|
||||||
|
const webhookSign = crypto
|
||||||
|
.createHmac("sha256", secretKey)
|
||||||
|
.update(JSON.stringify(payload))
|
||||||
|
.digest("hex");
|
||||||
|
headers["x-infisical-signature"] = `t=${payload["timestamp"]};${webhookSign}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = await axios.post(url, payload, { headers });
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWebhookPayload = (
|
||||||
|
eventName: string,
|
||||||
|
workspaceId: string,
|
||||||
|
environment: string,
|
||||||
|
secretPath?: string
|
||||||
|
) => ({
|
||||||
|
event: eventName,
|
||||||
|
project: {
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secretPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const triggerWebhook = async (
|
||||||
|
workspaceId: string,
|
||||||
|
environment: string,
|
||||||
|
secretPath: string
|
||||||
|
) => {
|
||||||
|
const webhooks = await Webhook.find({ workspace: workspaceId, environment, isDisabled: false });
|
||||||
|
// TODO(akhilmhdh): implement retry policy later, for that a cron job based approach is needed
|
||||||
|
// for exponential backoff
|
||||||
|
const toBeTriggeredHooks = webhooks.filter(({ secretPath: hookSecretPath }) =>
|
||||||
|
picomatch.isMatch(secretPath, hookSecretPath, { strictSlashes: false })
|
||||||
|
);
|
||||||
|
const webhooksTriggered = await Promise.allSettled(
|
||||||
|
toBeTriggeredHooks.map((hook) =>
|
||||||
|
triggerWebhookRequest(
|
||||||
|
hook,
|
||||||
|
getWebhookPayload("secrets.modified", workspaceId, environment, secretPath)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const successWebhooks: Types.ObjectId[] = [];
|
||||||
|
const failedWebhooks: Array<{ id: Types.ObjectId; error: string }> = [];
|
||||||
|
webhooksTriggered.forEach((data, index) => {
|
||||||
|
if (data.status === "rejected") {
|
||||||
|
failedWebhooks.push({ id: toBeTriggeredHooks[index]._id, error: data.reason.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successWebhooks.push(toBeTriggeredHooks[index]._id);
|
||||||
|
});
|
||||||
|
// dont remove the workspaceid and environment filter. its used to reduce the dataset before $in check
|
||||||
|
await Webhook.bulkWrite([
|
||||||
|
{
|
||||||
|
updateMany: {
|
||||||
|
filter: { workspace: workspaceId, environment, _id: { $in: successWebhooks } },
|
||||||
|
update: { lastStatus: "success", lastRunErrorMessage: null }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...failedWebhooks.map(({ id, error }) => ({
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
_id: id
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
lastStatus: "failed",
|
||||||
|
lastRunErrorMessage: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
]);
|
||||||
|
};
|
||||||
@@ -2,17 +2,21 @@ import DatabaseService from "./DatabaseService";
|
|||||||
// import { logTelemetryMessage, getPostHogClient } from './TelemetryService';
|
// import { logTelemetryMessage, getPostHogClient } from './TelemetryService';
|
||||||
import TelemetryService from "./TelemetryService";
|
import TelemetryService from "./TelemetryService";
|
||||||
import BotService from "./BotService";
|
import BotService from "./BotService";
|
||||||
|
import BotOrgService from "./BotOrgService";
|
||||||
import EventService from "./EventService";
|
import EventService from "./EventService";
|
||||||
import IntegrationService from "./IntegrationService";
|
import IntegrationService from "./IntegrationService";
|
||||||
import TokenService from "./TokenService";
|
import TokenService from "./TokenService";
|
||||||
import SecretService from "./SecretService";
|
import SecretService from "./SecretService";
|
||||||
|
import GithubSecretScanningService from "./GithubSecretScanningService"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
TelemetryService,
|
TelemetryService,
|
||||||
DatabaseService,
|
DatabaseService,
|
||||||
BotService,
|
BotService,
|
||||||
EventService,
|
BotOrgService,
|
||||||
IntegrationService,
|
EventService,
|
||||||
TokenService,
|
IntegrationService,
|
||||||
SecretService,
|
TokenService,
|
||||||
}
|
SecretService,
|
||||||
|
GithubSecretScanningService
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import nodemailer from "nodemailer";
|
|||||||
import {
|
import {
|
||||||
SMTP_HOST_GMAIL,
|
SMTP_HOST_GMAIL,
|
||||||
SMTP_HOST_MAILGUN,
|
SMTP_HOST_MAILGUN,
|
||||||
|
SMTP_HOST_OFFICE365,
|
||||||
SMTP_HOST_SENDGRID,
|
SMTP_HOST_SENDGRID,
|
||||||
SMTP_HOST_SOCKETLABS,
|
SMTP_HOST_SOCKETLABS,
|
||||||
SMTP_HOST_ZOHOMAIL,
|
SMTP_HOST_ZOHOMAIL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import SMTPConnection from "nodemailer/lib/smtp-connection";
|
import SMTPConnection from "nodemailer/lib/smtp-connection";
|
||||||
import * as Sentry from "@sentry/node";
|
import * as Sentry from "@sentry/node";
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
getSmtpSecure,
|
getSmtpSecure,
|
||||||
getSmtpUsername,
|
getSmtpUsername,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
|
import { getLogger } from "../utils/logger";
|
||||||
|
|
||||||
export const initSmtp = async () => {
|
export const initSmtp = async () => {
|
||||||
const mailOpts: SMTPConnection.Options = {
|
const mailOpts: SMTPConnection.Options = {
|
||||||
@@ -58,6 +60,12 @@ export const initSmtp = async () => {
|
|||||||
ciphers: "TLSv1.2",
|
ciphers: "TLSv1.2",
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SMTP_HOST_OFFICE365:
|
||||||
|
mailOpts.requireTLS = true;
|
||||||
|
mailOpts.tls = {
|
||||||
|
ciphers: "TLSv1.2"
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if ((await getSmtpHost()).includes("amazonaws.com")) {
|
if ((await getSmtpHost()).includes("amazonaws.com")) {
|
||||||
mailOpts.tls = {
|
mailOpts.tls = {
|
||||||
@@ -73,10 +81,12 @@ export const initSmtp = async () => {
|
|||||||
const transporter = nodemailer.createTransport(mailOpts);
|
const transporter = nodemailer.createTransport(mailOpts);
|
||||||
transporter
|
transporter
|
||||||
.verify()
|
.verify()
|
||||||
.then((err) => {
|
.then(async () => {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureMessage("SMTP - Successfully connected");
|
Sentry.captureMessage("SMTP - Successfully connected");
|
||||||
console.log("SMTP - Successfully connected")
|
(await getLogger("backend-main")).info(
|
||||||
|
"SMTP - Successfully connected"
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(async (err) => {
|
.catch(async (err) => {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<title>Incident alert: secret leaked</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h3>Infisical has uncovered {{numberOfSecrets}} secret(s) from your recent push</h3>
|
||||||
|
<p><a href="https://app.infisical.com/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
||||||
|
<p>You are receiving this notification because one or more secret leaks have been detected in a recent commit pushed
|
||||||
|
by {{pusher_name}} ({{pusher_email}}). If
|
||||||
|
these are test secrets, please add `infisical-scan:ignore` at the end of the line containing the secret as comment
|
||||||
|
in the given programming. This will prevent future notifications from being sent out for those secret(s).</p>
|
||||||
|
|
||||||
|
<p>If these are production secrets, please rotate them immediately.</p>
|
||||||
|
|
||||||
|
<p>Once you have taken action, be sure to update the status of the risk in your <a
|
||||||
|
href="https://app.infisical.com/">Infisical
|
||||||
|
dashboard</a>.</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user