mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-21 02:27:03 +00:00
Compare commits
95 Commits
add-gitlea
...
infisical-
Author | SHA1 | Date | |
---|---|---|---|
8e5db3ee2f | |||
6b0e0f70d2 | |||
1fb9aad08a | |||
61a09d817b | |||
57b8ed4eef | |||
c3a1d03a9b | |||
11afb6db51 | |||
200d9de740 | |||
17060b22d7 | |||
c730280eff | |||
c45120e6e9 | |||
e1e2eb7c3b | |||
7812061e66 | |||
ca41c65fe0 | |||
d8c15a366d | |||
df9efa65e7 | |||
1c5616e3b6 | |||
27030138ec | |||
c37ce4eaea | |||
5aa367fe54 | |||
17647587f9 | |||
f3dc7fcf7b | |||
e65c6568e1 | |||
9d40a96633 | |||
859fe09ac6 | |||
d0d6419d4d | |||
8b05ce11f7 | |||
a7fb0786f9 | |||
f2de1778cb | |||
952cf47b9a | |||
1d17596af1 | |||
01385687e0 | |||
d2e3aa15b0 | |||
96607153dc | |||
a8502377c7 | |||
5aa99001cc | |||
83dd35299c | |||
b5b2f402ad | |||
ec34572087 | |||
7f7d120c2f | |||
899d46514c | |||
658df21189 | |||
8341faddc5 | |||
8e3a23e6d8 | |||
1c89474159 | |||
2f765600b1 | |||
d9057216b5 | |||
6aab90590f | |||
f7466d4855 | |||
ea2565ed35 | |||
4586656b85 | |||
e4953398df | |||
7722231656 | |||
845a476974 | |||
fc19a17f4b | |||
0890b1912f | |||
82ecc2d7dc | |||
460bdbb91c | |||
446a63a917 | |||
d67cb7b507 | |||
9f40266f5c | |||
8af8a1d3d5 | |||
631423fbc8 | |||
4383779377 | |||
20294ee233 | |||
c5a924e935 | |||
429bfd27b2 | |||
c99c873d78 | |||
092a6911ce | |||
a9b642e618 | |||
919ddf5de2 | |||
89a89af4e6 | |||
960063e61a | |||
abf4eaf6db | |||
739f97f5c9 | |||
faed5c1821 | |||
c95598aaa6 | |||
e791684f4d | |||
d32c5fb869 | |||
abbf1918dc | |||
876d0119d3 | |||
6d70dc437e | |||
174e22a2bc | |||
f4815641d8 | |||
5b95c255ec | |||
3123f6fc1f | |||
a913cd97a4 | |||
7aa5ef844c | |||
f011d61167 | |||
87e047a152 | |||
3d3d7c9821 | |||
5eeda6272c | |||
c766686670 | |||
099cee7f39 | |||
fd9387a25e |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
backend/node_modules
|
||||
frontend/node_modules
|
23
.github/workflows/check-be-pull-request.yml
vendored
23
.github/workflows/check-be-pull-request.yml
vendored
@ -13,6 +13,7 @@ jobs:
|
||||
check-be-pr:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
@ -26,17 +27,17 @@ jobs:
|
||||
- name: 📦 Install dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
- name: 🧪 Run tests
|
||||
run: npm run test:ci
|
||||
working-directory: backend
|
||||
- name: 📁 Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: be-test-results
|
||||
path: |
|
||||
./backend/reports
|
||||
./backend/coverage
|
||||
# - name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
# - name: 📁 Upload test results
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: always()
|
||||
# with:
|
||||
# name: be-test-results
|
||||
# path: |
|
||||
# ./backend/reports
|
||||
# ./backend/coverage
|
||||
- name: 🏗️ Run build
|
||||
run: npm run build
|
||||
working-directory: backend
|
||||
|
29
.github/workflows/check-fe-pull-request.yml
vendored
29
.github/workflows/check-fe-pull-request.yml
vendored
@ -2,40 +2,35 @@ name: Check Frontend Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize ]
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
- '!frontend/README.md'
|
||||
- '!frontend/.*'
|
||||
- 'frontend/.eslintrc.js'
|
||||
|
||||
- "frontend/**"
|
||||
- "!frontend/README.md"
|
||||
- "!frontend/.*"
|
||||
- "frontend/.eslintrc.js"
|
||||
|
||||
jobs:
|
||||
|
||||
check-fe-pr:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
-
|
||||
name: ☁️ Checkout source
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: 🔧 Setup Node 16
|
||||
- name: 🔧 Setup Node 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
node-version: "16"
|
||||
cache: "npm"
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
-
|
||||
name: 📦 Install dependencies
|
||||
- name: 📦 Install dependencies
|
||||
run: npm ci --only-production --ignore-scripts
|
||||
working-directory: frontend
|
||||
# -
|
||||
# name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: frontend
|
||||
-
|
||||
name: 🏗️ Run build
|
||||
- name: 🏗️ Run build
|
||||
run: npm run build
|
||||
working-directory: frontend
|
||||
|
1
.github/workflows/release_build.yml
vendored
1
.github/workflows/release_build.yml
vendored
@ -46,6 +46,7 @@ jobs:
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
|
||||
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
|
@ -18,7 +18,9 @@ monorepo:
|
||||
builds:
|
||||
- id: darwin-build
|
||||
binary: infisical
|
||||
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
ldflags:
|
||||
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
|
||||
flags:
|
||||
- -trimpath
|
||||
env:
|
||||
@ -36,7 +38,9 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
binary: infisical
|
||||
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
ldflags:
|
||||
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
|
@ -3,3 +3,5 @@
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
|
||||
infisical scan git-changes --staged -v
|
||||
|
5
.pre-commit-config.yaml
Normal file
5
.pre-commit-config.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.16.3
|
||||
hooks:
|
||||
- id: gitleaks
|
6
.pre-commit-hooks.yaml
Normal file
6
.pre-commit-hooks.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
- id: infisical-scan
|
||||
name: Scan for hardcoded secrets
|
||||
description: Will scan for hardcoded secrets using Infisical CLI
|
||||
entry: infisical scan git-changes --verbose --redact --staged
|
||||
language: golang
|
||||
pass_filenames: false
|
@ -77,7 +77,7 @@ RUN npm ci --only-production
|
||||
COPY --from=backend-build /app .
|
||||
|
||||
# Production stage
|
||||
FROM node:14-alpine AS production
|
||||
FROM node:16-alpine AS production
|
||||
|
||||
WORKDIR /
|
||||
|
||||
|
22
README.md
22
README.md
@ -25,7 +25,7 @@
|
||||
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
|
||||
</a>
|
||||
<a href="https://cloudsmith.io/~infisical/repos/">
|
||||
<img src="https://img.shields.io/badge/Downloads-150.8k-orange" alt="Cloudsmith downloads" />
|
||||
<img src="https://img.shields.io/badge/Downloads-240.2k-orange" alt="Cloudsmith downloads" />
|
||||
</a>
|
||||
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">
|
||||
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
|
||||
@ -55,7 +55,7 @@ We're on a mission to make secret management more accessible to everyone, not ju
|
||||
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)** to record every action taken in a project
|
||||
- **Role-based Access Controls** per environment
|
||||
- [**Simple on-premise deployments** to AWS and Digital Ocean](https://infisical.com/docs/self-hosting/overview)
|
||||
- [**2FA**](https://infisical.com/docs/documentation/platform/mfa) with more options coming soon
|
||||
- [**Secret Scanning**](https://infisical.com/docs/cli/scanning-overview)
|
||||
|
||||
And much more.
|
||||
|
||||
@ -89,6 +89,24 @@ git clone https://github.com/Infisical/infisical && cd infisical && copy .env.ex
|
||||
|
||||
Create an account at `http://localhost:80`
|
||||
|
||||
### Scan and prevent secret leaks
|
||||
On top managing secrets with Infisical, you can also scan for over 140+ secret types in your files, directories and git repositories.
|
||||
|
||||
To scan your full git history, run:
|
||||
|
||||
```
|
||||
infisical scan --verbose
|
||||
```
|
||||
|
||||
Install pre commit hook to scan each commit before you push to your repository
|
||||
|
||||
```
|
||||
infisical scan install --pre-commit-hook
|
||||
```
|
||||
|
||||
Lean about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
|
||||
|
||||
|
||||
## Open-source vs. paid
|
||||
|
||||
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license in the future.
|
||||
|
459
backend/package-lock.json
generated
459
backend/package-lock.json
generated
@ -9,16 +9,16 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.312.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
||||
"@godaddy/terminus": "^4.12.0",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.41.0",
|
||||
"@sentry/node": "^7.49.0",
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"argon2": "^0.30.3",
|
||||
"await-to-js": "^3.0.0",
|
||||
"aws-sdk": "^2.1360.0",
|
||||
"aws-sdk": "^2.1364.0",
|
||||
"axios": "^1.3.5",
|
||||
"axios-retry": "^3.4.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
@ -33,13 +33,14 @@
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"infisical-node": "^1.1.3",
|
||||
"infisical-node": "^1.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.10.5",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.6.0",
|
||||
"query-string": "^7.1.3",
|
||||
@ -234,15 +235,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager": {
|
||||
"version": "3.312.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.312.0.tgz",
|
||||
"integrity": "sha512-8MhjP8xXU9pWnWi43YvsU/EIj9R4ABUo+XpNsxHkR9KTsG2QR/0h/xjQO9hqZOukZYW8DwdwLRxH81FsDTLYEQ==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.319.0.tgz",
|
||||
"integrity": "sha512-3YPzBvr/hRlH3MbivWYGdvY4EMLwBVAE93gZjfIrkjvpufd/OKX5OoO8Q0EhOkfnc2VIw0DMT2x6ERtgXuVc4Q==",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
"@aws-sdk/client-sts": "3.312.0",
|
||||
"@aws-sdk/client-sts": "3.319.0",
|
||||
"@aws-sdk/config-resolver": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.319.0",
|
||||
"@aws-sdk/fetch-http-handler": "3.310.0",
|
||||
"@aws-sdk/hash-node": "3.310.0",
|
||||
"@aws-sdk/invalid-dependency": "3.310.0",
|
||||
@ -255,19 +256,19 @@
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-signing": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -292,9 +293,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.310.0.tgz",
|
||||
"integrity": "sha512-netFap3Mp9I7bzAjsswHPA5WEbQtNMmXvW9/IVb7tmf85/esXCWindtyI43e/Xerut9ZVyEACPBFn30CLLE2xQ==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.319.0.tgz",
|
||||
"integrity": "sha512-g46KgAjRiYBS8Oi85DPwSAQpt+Hgmw/YFgGVwZqMfTL70KNJwLFKRa5D9UocQd7t7OjPRdKF7g0Gp5peyAK9dw==",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
@ -310,19 +311,19 @@
|
||||
"@aws-sdk/middleware-retry": "3.310.0",
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -334,9 +335,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso-oidc": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.310.0.tgz",
|
||||
"integrity": "sha512-3GKaRSfMD3OiYWGa+qg5KvJw0nLV0Vu7zRiulLuKDvgmWw3SNJKn3frWlmq/bKFUKahLsV8zozbeJItxtKAD6g==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.319.0.tgz",
|
||||
"integrity": "sha512-GJBgT/tephRZY3oTbDBMv+G9taoqKUIvGPn+7shmzz2P1SerutsRSfKfDXV+VptPNRoGmjjCLPmWjMFYbFKILQ==",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
@ -352,19 +353,19 @@
|
||||
"@aws-sdk/middleware-retry": "3.310.0",
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -376,14 +377,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sts": {
|
||||
"version": "3.312.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.312.0.tgz",
|
||||
"integrity": "sha512-t0U7vRvWaMjrzBUo6tPrHe6HE97Blqx+b4GOjFbcbLtzxLlcRfhnWJik0Lp8hJtVqzNoN5mL4OeYgK7CRpL/Sw==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.319.0.tgz",
|
||||
"integrity": "sha512-PRGGKCSKtyM3x629J9j4DMsH1cQT8UGW+R67u9Q5HrMK05gfjpmg+X1DQ3pgve4D8MI4R/Cm3NkYl2eUTbQHQg==",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
"@aws-sdk/config-resolver": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.319.0",
|
||||
"@aws-sdk/fetch-http-handler": "3.310.0",
|
||||
"@aws-sdk/hash-node": "3.310.0",
|
||||
"@aws-sdk/invalid-dependency": "3.310.0",
|
||||
@ -397,19 +398,19 @@
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-signing": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -464,14 +465,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.310.0.tgz",
|
||||
"integrity": "sha512-gtRz7I+4BBpwZ3tc6UIt5lQuiAFnkpOibxHh95x1M6HDxBjm+uqD6RPZYVH+dULZPYXOtOTsHV0IGjrcV0sSRg==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.319.0.tgz",
|
||||
"integrity": "sha512-pzx388Fw1KlSgmIMUyRY8DJVYM3aXpwzjprD4RiQVPJeAI+t7oQmEvd2FiUZEuHDjWXcuonxgU+dk7i7HUk/HQ==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "3.310.0",
|
||||
"@aws-sdk/credential-provider-imds": "3.310.0",
|
||||
"@aws-sdk/credential-provider-process": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.319.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.310.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
@ -483,15 +484,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.310.0.tgz",
|
||||
"integrity": "sha512-FrOztUcOq2Sp32xGtJvxfvdlmuAeoxIu/AElHzV1bkx6Pzo9DkQBhXrSQ+JFSpI++weOD4ZGFhAvgbgUOT4VAg==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.319.0.tgz",
|
||||
"integrity": "sha512-DS4a0Rdd7ZtMshoeE+zuSgbC05YBcdzd0h89u/eX+1Yqx+HCjeb8WXkbXsz0Mwx8q9TE04aS8f6Bw9J4x4mO5g==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "3.310.0",
|
||||
"@aws-sdk/credential-provider-imds": "3.310.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.310.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.319.0",
|
||||
"@aws-sdk/credential-provider-process": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.319.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.310.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
@ -517,14 +518,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.310.0.tgz",
|
||||
"integrity": "sha512-nXkpT8mrM/wRqSiz/a4p9U2UrOKyfZXhbPHIHyQj8K+uLjsYS+WPuH287J4A5Q57A6uarTrj5RjHmVeZVLaHmg==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.319.0.tgz",
|
||||
"integrity": "sha512-gAUnWH41lxkIbANXu+Rz5zS0Iavjjmpf3C56vAMT7oaYZ3Cg/Ys5l2SwAucQGOCA2DdS2hDiSI8E+Yhr4F5toA==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sso": "3.310.0",
|
||||
"@aws-sdk/client-sso": "3.319.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
"@aws-sdk/token-providers": "3.310.0",
|
||||
"@aws-sdk/token-providers": "3.319.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
@ -727,13 +728,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.310.0.tgz",
|
||||
"integrity": "sha512-x3IOwSwSbwKidlxRk3CNVHVUb06SRuaELxggCaR++QVI8NU6qD/l4VHXKVRvbTHiC/cYxXE/GaBDgQVpDR7V/g==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.319.0.tgz",
|
||||
"integrity": "sha512-ytaLx2dlR5AdMSne6FuDCISVg8hjyKj+cHU20b2CRA/E/z+XXrLrssp4JrCgizRKPPUep0psMIa22Zd6osTT5Q==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -856,9 +857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/smithy-client": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.310.0.tgz",
|
||||
"integrity": "sha512-UHMFvhoB2RLzsTb0mQe1ofvBUg/+/JEu1uptavxf/hEpEKZnRAaHH5FNkTG+mbFd/olay/QFjqNcMD6t8LcsNQ==",
|
||||
"version": "3.316.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.316.0.tgz",
|
||||
"integrity": "sha512-6YXOKbRnXeS8r8RWzuL6JMBolDYM5Wa4fD/VY6x/wK78i2xErHOvqzHgyyeLI1MMw4uqyd4wRNJNWC9TMPduXw==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
@ -869,11 +870,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.310.0.tgz",
|
||||
"integrity": "sha512-G1JvB+2v8k900VJFkKVQXgLGF50ShOEIPxfK1gSQLkSU85vPwGIAANs1KvnlW08FsNbWp3+sKca4kfYKsooXMw==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.319.0.tgz",
|
||||
"integrity": "sha512-5utg6VL6Pl0uiLUn8ZJPYYxzCb9VRPsgJmGXktRUwq0YlTJ6ABcaxTXwZcC++sjh/qyCQDK5PPLNU5kIBttHMQ==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sso-oidc": "3.310.0",
|
||||
"@aws-sdk/client-sso-oidc": "3.319.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
@ -959,9 +960,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-defaults-mode-browser": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.310.0.tgz",
|
||||
"integrity": "sha512-Mr2AoQsjAYNM5oAS2YJlYJqhiCvkFV/hu48slOZgbY4G7ueW4cM0DPkR16wqjcRCGqZ4JmAZB8Q5R0DMrLjhOQ==",
|
||||
"version": "3.316.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.316.0.tgz",
|
||||
"integrity": "sha512-6FSqLhYmaihtH2n1s4b2rlLW0ABU8N6VZIfzLfe2ING4PF0MzfaMMhnTFUHVXfKCVGoR8yP6iyFTRCyHGVEL1w==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
@ -973,9 +974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-defaults-mode-node": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.310.0.tgz",
|
||||
"integrity": "sha512-JyBlvhQGR8w8NpFRZZXRVTDesafFKTu/gTWjcoxP7twa+fYHSIgPPFGnlcJ/iHaucjamSaWi5EQ+YQmnSZ8yHA==",
|
||||
"version": "3.316.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.316.0.tgz",
|
||||
"integrity": "sha512-dkYy10hdjPSScXXvnjGpZpnJxllkb6ICHgLMwZ4JczLHhPM12T/4PQ758YN8HS+muiYDGX1Bl2z1jd/bMcewBQ==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/config-resolver": "3.310.0",
|
||||
"@aws-sdk/credential-provider-imds": "3.310.0",
|
||||
@ -989,9 +990,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-endpoints": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.310.0.tgz",
|
||||
"integrity": "sha512-zG+/d/O5KPmAaeOMPd6bW1abifdT0H03f42keLjYEoRZzYtHPC5DuPE0UayiWGckI6BCDgy0sRKXCYS49UNFaQ==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.319.0.tgz",
|
||||
"integrity": "sha512-3I64UMoYA2e2++oOUJXRcFtYLpLylnZFRltWfPo1B3dLlf+MIWat9djT+mMus+hW1ntLsvAIVu1hLVePJC0gvw==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"tslib": "^2.5.0"
|
||||
@ -3654,13 +3655,13 @@
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@sentry-internal/tracing": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.47.0.tgz",
|
||||
"integrity": "sha512-udpHnCzF8DQsWf0gQwd0XFGp6Y8MOiwnl8vGt2ohqZGS3m1+IxoRLXsSkD8qmvN6KKDnwbaAvYnK0z0L+AW95g==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.49.0.tgz",
|
||||
"integrity": "sha512-ESh3+ZneQk/3HESTUmIPNrW5GVPu/HrRJU+eAJJto74vm+6vP7zDn2YV2gJ1w18O/37nc7W/bVCgZJlhZ3cwew==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.47.0",
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry/core": "7.49.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -3668,12 +3669,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/tracing/node_modules/@sentry/core": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.47.0.tgz",
|
||||
"integrity": "sha512-EFhZhKdMu7wKmWYZwbgTi8FNZ7Fq+HdlXiZWNz51Bqe3pHmfAkdHtAEs0Buo0v623MKA0CA4EjXIazGUM34XTg==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.49.0.tgz",
|
||||
"integrity": "sha512-AlSnCYgfEbvK8pkNluUkmdW/cD9UpvOVCa+ERQswXNRkAv5aDGCL6Ihv6fnIajE++BYuwZh0+HwZUBVKTFzoZg==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -3681,19 +3682,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/tracing/node_modules/@sentry/types": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.47.0.tgz",
|
||||
"integrity": "sha512-GxXocplN0j1+uczovHrfkykl9wvkamDtWxlPUQgyGlbLGZn+UH1Y79D4D58COaFWGEZdSNKr62gZAjfEYu9nQA==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.49.0.tgz",
|
||||
"integrity": "sha512-9yXXh7iv76+O6h2ONUVx0wsL1auqJFWez62mTjWk4350SgMmWp/zUkBxnVXhmcYqscz/CepC+Loz9vITLXtgxg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/tracing/node_modules/@sentry/utils": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.47.0.tgz",
|
||||
"integrity": "sha512-A89SaOLp6XeZfByeYo2C8Ecye/YAtk/gENuyOUhQEdMulI6mZdjqtHAp7pTMVgkBc/YNARVuoa+kR/IdRrTPkQ==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.49.0.tgz",
|
||||
"integrity": "sha512-JdC9yGnOgev4ISJVwmIoFsk8Zx0psDZJAj2DV7x4wMZsO6QK+YjC7G3mUED/S5D5lsrkBZ/3uvQQhr8DQI4UcQ==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -3724,14 +3725,14 @@
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@sentry/node": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.47.0.tgz",
|
||||
"integrity": "sha512-LTg2r5EV9yh4GLYDF+ViSltR9LLj/pcvk8YhANJcMO3Fp//xh8njcdU0FC2yNthUREawYDzAsVzLyCYJfV0H1A==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.49.0.tgz",
|
||||
"integrity": "sha512-KLIrqcbKk4yR3g8fjl87Eyv4M9j4YI6b7sqVAZYj3FrX3mC6JQyGdlDfUpSKy604n1iAdr6OuUp5f9x7jPJaeQ==",
|
||||
"dependencies": {
|
||||
"@sentry-internal/tracing": "7.47.0",
|
||||
"@sentry/core": "7.47.0",
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry-internal/tracing": "7.49.0",
|
||||
"@sentry/core": "7.49.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
@ -3742,12 +3743,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@sentry/core": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.47.0.tgz",
|
||||
"integrity": "sha512-EFhZhKdMu7wKmWYZwbgTi8FNZ7Fq+HdlXiZWNz51Bqe3pHmfAkdHtAEs0Buo0v623MKA0CA4EjXIazGUM34XTg==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.49.0.tgz",
|
||||
"integrity": "sha512-AlSnCYgfEbvK8pkNluUkmdW/cD9UpvOVCa+ERQswXNRkAv5aDGCL6Ihv6fnIajE++BYuwZh0+HwZUBVKTFzoZg==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -3755,19 +3756,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@sentry/types": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.47.0.tgz",
|
||||
"integrity": "sha512-GxXocplN0j1+uczovHrfkykl9wvkamDtWxlPUQgyGlbLGZn+UH1Y79D4D58COaFWGEZdSNKr62gZAjfEYu9nQA==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.49.0.tgz",
|
||||
"integrity": "sha512-9yXXh7iv76+O6h2ONUVx0wsL1auqJFWez62mTjWk4350SgMmWp/zUkBxnVXhmcYqscz/CepC+Loz9vITLXtgxg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/node/node_modules/@sentry/utils": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.47.0.tgz",
|
||||
"integrity": "sha512-A89SaOLp6XeZfByeYo2C8Ecye/YAtk/gENuyOUhQEdMulI6mZdjqtHAp7pTMVgkBc/YNARVuoa+kR/IdRrTPkQ==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.49.0.tgz",
|
||||
"integrity": "sha512-JdC9yGnOgev4ISJVwmIoFsk8Zx0psDZJAj2DV7x4wMZsO6QK+YjC7G3mUED/S5D5lsrkBZ/3uvQQhr8DQI4UcQ==",
|
||||
"dependencies": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -4824,9 +4825,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-sdk": {
|
||||
"version": "2.1360.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1360.0.tgz",
|
||||
"integrity": "sha512-wW1CviH1s6bl5+wO+KM7aSc3yy6cQPJT85Fd4rQgrn0uwfjg9fx7KJ0FRhv+eU4DabkRjcSMlKo1IGhARmT6Tw==",
|
||||
"version": "2.1364.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1364.0.tgz",
|
||||
"integrity": "sha512-PFoq9Rnu0DVi07wZ/SjKlrJDwso8AxE5q/ufLdNtINZPaSkB92OqKYVdlfQ4srsH2ala2NCrg8gFX98SCmqW3w==",
|
||||
"dependencies": {
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
@ -6966,13 +6967,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/infisical-node": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.1.3.tgz",
|
||||
"integrity": "sha512-MLcZQ/zdpCYFRbj50Tn4Qm58wSKPQfKc3xX4I0c3NnFZvMGd50wnoG1jkkNKjKiYU5h7QDpOg0XZSvlU7yuG6g==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.2.1.tgz",
|
||||
"integrity": "sha512-zEB0w5+1O0mv9qc68bq4f9jDjrtwdbqjJebnwodgy8U1XZElDXeMDQgSMCtgYan7JRmVlH6s/LM8X7kUF+67ZA==",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"node-cache": "^5.1.2",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1"
|
||||
}
|
||||
@ -13398,15 +13398,15 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/client-secrets-manager": {
|
||||
"version": "3.312.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.312.0.tgz",
|
||||
"integrity": "sha512-8MhjP8xXU9pWnWi43YvsU/EIj9R4ABUo+XpNsxHkR9KTsG2QR/0h/xjQO9hqZOukZYW8DwdwLRxH81FsDTLYEQ==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.319.0.tgz",
|
||||
"integrity": "sha512-3YPzBvr/hRlH3MbivWYGdvY4EMLwBVAE93gZjfIrkjvpufd/OKX5OoO8Q0EhOkfnc2VIw0DMT2x6ERtgXuVc4Q==",
|
||||
"requires": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
"@aws-sdk/client-sts": "3.312.0",
|
||||
"@aws-sdk/client-sts": "3.319.0",
|
||||
"@aws-sdk/config-resolver": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.319.0",
|
||||
"@aws-sdk/fetch-http-handler": "3.310.0",
|
||||
"@aws-sdk/hash-node": "3.310.0",
|
||||
"@aws-sdk/invalid-dependency": "3.310.0",
|
||||
@ -13419,19 +13419,19 @@
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-signing": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -13450,9 +13450,9 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/client-sso": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.310.0.tgz",
|
||||
"integrity": "sha512-netFap3Mp9I7bzAjsswHPA5WEbQtNMmXvW9/IVb7tmf85/esXCWindtyI43e/Xerut9ZVyEACPBFn30CLLE2xQ==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.319.0.tgz",
|
||||
"integrity": "sha512-g46KgAjRiYBS8Oi85DPwSAQpt+Hgmw/YFgGVwZqMfTL70KNJwLFKRa5D9UocQd7t7OjPRdKF7g0Gp5peyAK9dw==",
|
||||
"requires": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
@ -13468,19 +13468,19 @@
|
||||
"@aws-sdk/middleware-retry": "3.310.0",
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -13489,9 +13489,9 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/client-sso-oidc": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.310.0.tgz",
|
||||
"integrity": "sha512-3GKaRSfMD3OiYWGa+qg5KvJw0nLV0Vu7zRiulLuKDvgmWw3SNJKn3frWlmq/bKFUKahLsV8zozbeJItxtKAD6g==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.319.0.tgz",
|
||||
"integrity": "sha512-GJBgT/tephRZY3oTbDBMv+G9taoqKUIvGPn+7shmzz2P1SerutsRSfKfDXV+VptPNRoGmjjCLPmWjMFYbFKILQ==",
|
||||
"requires": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
@ -13507,19 +13507,19 @@
|
||||
"@aws-sdk/middleware-retry": "3.310.0",
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -13528,14 +13528,14 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/client-sts": {
|
||||
"version": "3.312.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.312.0.tgz",
|
||||
"integrity": "sha512-t0U7vRvWaMjrzBUo6tPrHe6HE97Blqx+b4GOjFbcbLtzxLlcRfhnWJik0Lp8hJtVqzNoN5mL4OeYgK7CRpL/Sw==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.319.0.tgz",
|
||||
"integrity": "sha512-PRGGKCSKtyM3x629J9j4DMsH1cQT8UGW+R67u9Q5HrMK05gfjpmg+X1DQ3pgve4D8MI4R/Cm3NkYl2eUTbQHQg==",
|
||||
"requires": {
|
||||
"@aws-crypto/sha256-browser": "3.0.0",
|
||||
"@aws-crypto/sha256-js": "3.0.0",
|
||||
"@aws-sdk/config-resolver": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.310.0",
|
||||
"@aws-sdk/credential-provider-node": "3.319.0",
|
||||
"@aws-sdk/fetch-http-handler": "3.310.0",
|
||||
"@aws-sdk/hash-node": "3.310.0",
|
||||
"@aws-sdk/invalid-dependency": "3.310.0",
|
||||
@ -13549,19 +13549,19 @@
|
||||
"@aws-sdk/middleware-serde": "3.310.0",
|
||||
"@aws-sdk/middleware-signing": "3.310.0",
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.310.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.319.0",
|
||||
"@aws-sdk/node-config-provider": "3.310.0",
|
||||
"@aws-sdk/node-http-handler": "3.310.0",
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.310.0",
|
||||
"@aws-sdk/smithy-client": "3.316.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/url-parser": "3.310.0",
|
||||
"@aws-sdk/util-base64": "3.310.0",
|
||||
"@aws-sdk/util-body-length-browser": "3.310.0",
|
||||
"@aws-sdk/util-body-length-node": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-defaults-mode-browser": "3.316.0",
|
||||
"@aws-sdk/util-defaults-mode-node": "3.316.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"@aws-sdk/util-retry": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.310.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.310.0",
|
||||
@ -13604,14 +13604,14 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.310.0.tgz",
|
||||
"integrity": "sha512-gtRz7I+4BBpwZ3tc6UIt5lQuiAFnkpOibxHh95x1M6HDxBjm+uqD6RPZYVH+dULZPYXOtOTsHV0IGjrcV0sSRg==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.319.0.tgz",
|
||||
"integrity": "sha512-pzx388Fw1KlSgmIMUyRY8DJVYM3aXpwzjprD4RiQVPJeAI+t7oQmEvd2FiUZEuHDjWXcuonxgU+dk7i7HUk/HQ==",
|
||||
"requires": {
|
||||
"@aws-sdk/credential-provider-env": "3.310.0",
|
||||
"@aws-sdk/credential-provider-imds": "3.310.0",
|
||||
"@aws-sdk/credential-provider-process": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.319.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.310.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
@ -13620,15 +13620,15 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/credential-provider-node": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.310.0.tgz",
|
||||
"integrity": "sha512-FrOztUcOq2Sp32xGtJvxfvdlmuAeoxIu/AElHzV1bkx6Pzo9DkQBhXrSQ+JFSpI++weOD4ZGFhAvgbgUOT4VAg==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.319.0.tgz",
|
||||
"integrity": "sha512-DS4a0Rdd7ZtMshoeE+zuSgbC05YBcdzd0h89u/eX+1Yqx+HCjeb8WXkbXsz0Mwx8q9TE04aS8f6Bw9J4x4mO5g==",
|
||||
"requires": {
|
||||
"@aws-sdk/credential-provider-env": "3.310.0",
|
||||
"@aws-sdk/credential-provider-imds": "3.310.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.310.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.319.0",
|
||||
"@aws-sdk/credential-provider-process": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.310.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.319.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.310.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
@ -13648,14 +13648,14 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.310.0.tgz",
|
||||
"integrity": "sha512-nXkpT8mrM/wRqSiz/a4p9U2UrOKyfZXhbPHIHyQj8K+uLjsYS+WPuH287J4A5Q57A6uarTrj5RjHmVeZVLaHmg==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.319.0.tgz",
|
||||
"integrity": "sha512-gAUnWH41lxkIbANXu+Rz5zS0Iavjjmpf3C56vAMT7oaYZ3Cg/Ys5l2SwAucQGOCA2DdS2hDiSI8E+Yhr4F5toA==",
|
||||
"requires": {
|
||||
"@aws-sdk/client-sso": "3.310.0",
|
||||
"@aws-sdk/client-sso": "3.319.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
"@aws-sdk/token-providers": "3.310.0",
|
||||
"@aws-sdk/token-providers": "3.319.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"tslib": "^2.5.0"
|
||||
}
|
||||
@ -13816,13 +13816,13 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.310.0.tgz",
|
||||
"integrity": "sha512-x3IOwSwSbwKidlxRk3CNVHVUb06SRuaELxggCaR++QVI8NU6qD/l4VHXKVRvbTHiC/cYxXE/GaBDgQVpDR7V/g==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.319.0.tgz",
|
||||
"integrity": "sha512-ytaLx2dlR5AdMSne6FuDCISVg8hjyKj+cHU20b2CRA/E/z+XXrLrssp4JrCgizRKPPUep0psMIa22Zd6osTT5Q==",
|
||||
"requires": {
|
||||
"@aws-sdk/protocol-http": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.310.0",
|
||||
"@aws-sdk/util-endpoints": "3.319.0",
|
||||
"tslib": "^2.5.0"
|
||||
}
|
||||
},
|
||||
@ -13915,9 +13915,9 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/smithy-client": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.310.0.tgz",
|
||||
"integrity": "sha512-UHMFvhoB2RLzsTb0mQe1ofvBUg/+/JEu1uptavxf/hEpEKZnRAaHH5FNkTG+mbFd/olay/QFjqNcMD6t8LcsNQ==",
|
||||
"version": "3.316.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.316.0.tgz",
|
||||
"integrity": "sha512-6YXOKbRnXeS8r8RWzuL6JMBolDYM5Wa4fD/VY6x/wK78i2xErHOvqzHgyyeLI1MMw4uqyd4wRNJNWC9TMPduXw==",
|
||||
"requires": {
|
||||
"@aws-sdk/middleware-stack": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
@ -13925,11 +13925,11 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/token-providers": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.310.0.tgz",
|
||||
"integrity": "sha512-G1JvB+2v8k900VJFkKVQXgLGF50ShOEIPxfK1gSQLkSU85vPwGIAANs1KvnlW08FsNbWp3+sKca4kfYKsooXMw==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.319.0.tgz",
|
||||
"integrity": "sha512-5utg6VL6Pl0uiLUn8ZJPYYxzCb9VRPsgJmGXktRUwq0YlTJ6ABcaxTXwZcC++sjh/qyCQDK5PPLNU5kIBttHMQ==",
|
||||
"requires": {
|
||||
"@aws-sdk/client-sso-oidc": "3.310.0",
|
||||
"@aws-sdk/client-sso-oidc": "3.319.0",
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/shared-ini-file-loader": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
@ -13997,9 +13997,9 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/util-defaults-mode-browser": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.310.0.tgz",
|
||||
"integrity": "sha512-Mr2AoQsjAYNM5oAS2YJlYJqhiCvkFV/hu48slOZgbY4G7ueW4cM0DPkR16wqjcRCGqZ4JmAZB8Q5R0DMrLjhOQ==",
|
||||
"version": "3.316.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.316.0.tgz",
|
||||
"integrity": "sha512-6FSqLhYmaihtH2n1s4b2rlLW0ABU8N6VZIfzLfe2ING4PF0MzfaMMhnTFUHVXfKCVGoR8yP6iyFTRCyHGVEL1w==",
|
||||
"requires": {
|
||||
"@aws-sdk/property-provider": "3.310.0",
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
@ -14008,9 +14008,9 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/util-defaults-mode-node": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.310.0.tgz",
|
||||
"integrity": "sha512-JyBlvhQGR8w8NpFRZZXRVTDesafFKTu/gTWjcoxP7twa+fYHSIgPPFGnlcJ/iHaucjamSaWi5EQ+YQmnSZ8yHA==",
|
||||
"version": "3.316.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.316.0.tgz",
|
||||
"integrity": "sha512-dkYy10hdjPSScXXvnjGpZpnJxllkb6ICHgLMwZ4JczLHhPM12T/4PQ758YN8HS+muiYDGX1Bl2z1jd/bMcewBQ==",
|
||||
"requires": {
|
||||
"@aws-sdk/config-resolver": "3.310.0",
|
||||
"@aws-sdk/credential-provider-imds": "3.310.0",
|
||||
@ -14021,9 +14021,9 @@
|
||||
}
|
||||
},
|
||||
"@aws-sdk/util-endpoints": {
|
||||
"version": "3.310.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.310.0.tgz",
|
||||
"integrity": "sha512-zG+/d/O5KPmAaeOMPd6bW1abifdT0H03f42keLjYEoRZzYtHPC5DuPE0UayiWGckI6BCDgy0sRKXCYS49UNFaQ==",
|
||||
"version": "3.319.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.319.0.tgz",
|
||||
"integrity": "sha512-3I64UMoYA2e2++oOUJXRcFtYLpLylnZFRltWfPo1B3dLlf+MIWat9djT+mMus+hW1ntLsvAIVu1hLVePJC0gvw==",
|
||||
"requires": {
|
||||
"@aws-sdk/types": "3.310.0",
|
||||
"tslib": "^2.5.0"
|
||||
@ -16063,37 +16063,37 @@
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"@sentry-internal/tracing": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.47.0.tgz",
|
||||
"integrity": "sha512-udpHnCzF8DQsWf0gQwd0XFGp6Y8MOiwnl8vGt2ohqZGS3m1+IxoRLXsSkD8qmvN6KKDnwbaAvYnK0z0L+AW95g==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.49.0.tgz",
|
||||
"integrity": "sha512-ESh3+ZneQk/3HESTUmIPNrW5GVPu/HrRJU+eAJJto74vm+6vP7zDn2YV2gJ1w18O/37nc7W/bVCgZJlhZ3cwew==",
|
||||
"requires": {
|
||||
"@sentry/core": "7.47.0",
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry/core": "7.49.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/core": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.47.0.tgz",
|
||||
"integrity": "sha512-EFhZhKdMu7wKmWYZwbgTi8FNZ7Fq+HdlXiZWNz51Bqe3pHmfAkdHtAEs0Buo0v623MKA0CA4EjXIazGUM34XTg==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.49.0.tgz",
|
||||
"integrity": "sha512-AlSnCYgfEbvK8pkNluUkmdW/cD9UpvOVCa+ERQswXNRkAv5aDGCL6Ihv6fnIajE++BYuwZh0+HwZUBVKTFzoZg==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.47.0.tgz",
|
||||
"integrity": "sha512-GxXocplN0j1+uczovHrfkykl9wvkamDtWxlPUQgyGlbLGZn+UH1Y79D4D58COaFWGEZdSNKr62gZAjfEYu9nQA=="
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.49.0.tgz",
|
||||
"integrity": "sha512-9yXXh7iv76+O6h2ONUVx0wsL1auqJFWez62mTjWk4350SgMmWp/zUkBxnVXhmcYqscz/CepC+Loz9vITLXtgxg=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.47.0.tgz",
|
||||
"integrity": "sha512-A89SaOLp6XeZfByeYo2C8Ecye/YAtk/gENuyOUhQEdMulI6mZdjqtHAp7pTMVgkBc/YNARVuoa+kR/IdRrTPkQ==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.49.0.tgz",
|
||||
"integrity": "sha512-JdC9yGnOgev4ISJVwmIoFsk8Zx0psDZJAj2DV7x4wMZsO6QK+YjC7G3mUED/S5D5lsrkBZ/3uvQQhr8DQI4UcQ==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
@ -16122,14 +16122,14 @@
|
||||
}
|
||||
},
|
||||
"@sentry/node": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.47.0.tgz",
|
||||
"integrity": "sha512-LTg2r5EV9yh4GLYDF+ViSltR9LLj/pcvk8YhANJcMO3Fp//xh8njcdU0FC2yNthUREawYDzAsVzLyCYJfV0H1A==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.49.0.tgz",
|
||||
"integrity": "sha512-KLIrqcbKk4yR3g8fjl87Eyv4M9j4YI6b7sqVAZYj3FrX3mC6JQyGdlDfUpSKy604n1iAdr6OuUp5f9x7jPJaeQ==",
|
||||
"requires": {
|
||||
"@sentry-internal/tracing": "7.47.0",
|
||||
"@sentry/core": "7.47.0",
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry-internal/tracing": "7.49.0",
|
||||
"@sentry/core": "7.49.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
@ -16137,26 +16137,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/core": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.47.0.tgz",
|
||||
"integrity": "sha512-EFhZhKdMu7wKmWYZwbgTi8FNZ7Fq+HdlXiZWNz51Bqe3pHmfAkdHtAEs0Buo0v623MKA0CA4EjXIazGUM34XTg==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.49.0.tgz",
|
||||
"integrity": "sha512-AlSnCYgfEbvK8pkNluUkmdW/cD9UpvOVCa+ERQswXNRkAv5aDGCL6Ihv6fnIajE++BYuwZh0+HwZUBVKTFzoZg==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/utils": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"@sentry/utils": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@sentry/types": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.47.0.tgz",
|
||||
"integrity": "sha512-GxXocplN0j1+uczovHrfkykl9wvkamDtWxlPUQgyGlbLGZn+UH1Y79D4D58COaFWGEZdSNKr62gZAjfEYu9nQA=="
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.49.0.tgz",
|
||||
"integrity": "sha512-9yXXh7iv76+O6h2ONUVx0wsL1auqJFWez62mTjWk4350SgMmWp/zUkBxnVXhmcYqscz/CepC+Loz9vITLXtgxg=="
|
||||
},
|
||||
"@sentry/utils": {
|
||||
"version": "7.47.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.47.0.tgz",
|
||||
"integrity": "sha512-A89SaOLp6XeZfByeYo2C8Ecye/YAtk/gENuyOUhQEdMulI6mZdjqtHAp7pTMVgkBc/YNARVuoa+kR/IdRrTPkQ==",
|
||||
"version": "7.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.49.0.tgz",
|
||||
"integrity": "sha512-JdC9yGnOgev4ISJVwmIoFsk8Zx0psDZJAj2DV7x4wMZsO6QK+YjC7G3mUED/S5D5lsrkBZ/3uvQQhr8DQI4UcQ==",
|
||||
"requires": {
|
||||
"@sentry/types": "7.47.0",
|
||||
"@sentry/types": "7.49.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
@ -16973,9 +16973,9 @@
|
||||
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.1360.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1360.0.tgz",
|
||||
"integrity": "sha512-wW1CviH1s6bl5+wO+KM7aSc3yy6cQPJT85Fd4rQgrn0uwfjg9fx7KJ0FRhv+eU4DabkRjcSMlKo1IGhARmT6Tw==",
|
||||
"version": "2.1364.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1364.0.tgz",
|
||||
"integrity": "sha512-PFoq9Rnu0DVi07wZ/SjKlrJDwso8AxE5q/ufLdNtINZPaSkB92OqKYVdlfQ4srsH2ala2NCrg8gFX98SCmqW3w==",
|
||||
"requires": {
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
@ -18579,13 +18579,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"infisical-node": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.1.3.tgz",
|
||||
"integrity": "sha512-MLcZQ/zdpCYFRbj50Tn4Qm58wSKPQfKc3xX4I0c3NnFZvMGd50wnoG1jkkNKjKiYU5h7QDpOg0XZSvlU7yuG6g==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.2.1.tgz",
|
||||
"integrity": "sha512-zEB0w5+1O0mv9qc68bq4f9jDjrtwdbqjJebnwodgy8U1XZElDXeMDQgSMCtgYan7JRmVlH6s/LM8X7kUF+67ZA==",
|
||||
"requires": {
|
||||
"axios": "^1.3.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"node-cache": "^5.1.2",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1"
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.312.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
||||
"@godaddy/terminus": "^4.12.0",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.41.0",
|
||||
"@sentry/node": "^7.49.0",
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"argon2": "^0.30.3",
|
||||
"await-to-js": "^3.0.0",
|
||||
"aws-sdk": "^2.1360.0",
|
||||
"aws-sdk": "^2.1364.0",
|
||||
"axios": "^1.3.5",
|
||||
"axios-retry": "^3.4.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
@ -24,13 +24,14 @@
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"infisical-node": "^1.1.3",
|
||||
"infisical-node": "^1.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.10.5",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.6.0",
|
||||
"query-string": "^7.1.3",
|
||||
|
@ -1,12 +1,19 @@
|
||||
import InfisicalClient from 'infisical-node';
|
||||
|
||||
const client = new InfisicalClient({
|
||||
export const client = new InfisicalClient({
|
||||
token: process.env.INFISICAL_TOKEN!
|
||||
});
|
||||
|
||||
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
|
||||
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue;
|
||||
export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue;
|
||||
export const getEncryptionKey = async () => {
|
||||
const secretValue = (await client.getSecret('ENCRYPTION_KEY')).secretValue;
|
||||
return secretValue === '' ? undefined : secretValue;
|
||||
}
|
||||
export const getRootEncryptionKey = async () => {
|
||||
const secretValue = (await client.getSecret('ROOT_ENCRYPTION_KEY')).secretValue;
|
||||
return secretValue === '' ? undefined : secretValue;
|
||||
}
|
||||
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue === 'true'
|
||||
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
|
||||
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';
|
||||
export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue;
|
||||
@ -45,12 +52,25 @@ export const getSmtpUsername = async () => (await client.getSecret('SMTP_USERNAM
|
||||
export const getSmtpPassword = async () => (await client.getSecret('SMTP_PASSWORD')).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 getLicenseKey = async () => {
|
||||
const secretValue = (await client.getSecret('LICENSE_KEY')).secretValue;
|
||||
return secretValue === '' ? undefined : secretValue;
|
||||
}
|
||||
export const getLicenseServerKey = async () => {
|
||||
const secretValue = (await client.getSecret('LICENSE_SERVER_KEY')).secretValue;
|
||||
return secretValue === '' ? undefined : secretValue;
|
||||
}
|
||||
export const getLicenseServerUrl = async () => (await client.getSecret('LICENSE_SERVER_URL')).secretValue || 'https://portal.infisical.com';
|
||||
|
||||
// TODO: deprecate from here
|
||||
export const getStripeProductStarter = async () => (await client.getSecret('STRIPE_PRODUCT_STARTER')).secretValue;
|
||||
export const getStripeProductPro = async () => (await client.getSecret('STRIPE_PRODUCT_PRO')).secretValue;
|
||||
export const getStripeProductTeam = async () => (await client.getSecret('STRIPE_PRODUCT_TEAM')).secretValue;
|
||||
export const getStripePublishableKey = async () => (await client.getSecret('STRIPE_PUBLISHABLE_KEY')).secretValue;
|
||||
export const getStripeSecretKey = async () => (await client.getSecret('STRIPE_SECRET_KEY')).secretValue;
|
||||
export const getStripeWebhookSecret = async () => (await client.getSecret('STRIPE_WEBHOOK_SECRET')).secretValue;
|
||||
|
||||
export const getTelemetryEnabled = async () => (await client.getSecret('TELEMETRY_ENABLED')).secretValue !== 'false' && true;
|
||||
export const getLoopsApiKey = async () => (await client.getSecret('LOOPS_API_KEY')).secretValue;
|
||||
export const getSmtpConfigured = async () => (await client.getSecret('SMTP_HOST')).secretValue == '' || (await client.getSecret('SMTP_HOST')).secretValue == undefined ? false : true
|
||||
|
@ -1,10 +1,24 @@
|
||||
import axios from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import {
|
||||
getLicenseServerKeyAuthToken,
|
||||
setLicenseServerKeyAuthToken,
|
||||
getLicenseKeyAuthToken,
|
||||
setLicenseKeyAuthToken
|
||||
} from './storage';
|
||||
import {
|
||||
getLicenseKey,
|
||||
getLicenseServerKey,
|
||||
getLicenseServerUrl
|
||||
} from './index';
|
||||
|
||||
const axiosInstance = axios.create();
|
||||
// should have JWT to interact with the license server
|
||||
export const licenseServerKeyRequest = axios.create();
|
||||
export const licenseKeyRequest = axios.create();
|
||||
export const standardRequest = axios.create();
|
||||
|
||||
// add retry functionality to the axios instance
|
||||
axiosRetry(axiosInstance, {
|
||||
axiosRetry(standardRequest, {
|
||||
retries: 3,
|
||||
retryDelay: axiosRetry.exponentialDelay, // exponential back-off delay between retries
|
||||
retryCondition: (error) => {
|
||||
@ -13,4 +27,98 @@ axiosRetry(axiosInstance, {
|
||||
},
|
||||
});
|
||||
|
||||
export default axiosInstance;
|
||||
export const refreshLicenseServerKeyToken = async () => {
|
||||
const licenseServerKey = await getLicenseServerKey();
|
||||
const licenseServerUrl = await getLicenseServerUrl();
|
||||
|
||||
const { data: { token } } = await standardRequest.post(
|
||||
`${licenseServerUrl}/api/auth/v1/license-server-login`, {},
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY': licenseServerKey
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setLicenseServerKeyAuthToken(token);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export const refreshLicenseKeyToken = async () => {
|
||||
const licenseKey = await getLicenseKey();
|
||||
const licenseServerUrl = await getLicenseServerUrl();
|
||||
|
||||
const { data: { token } } = await standardRequest.post(
|
||||
`${licenseServerUrl}/api/auth/v1/license-login`, {},
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY': licenseKey
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setLicenseKeyAuthToken(token);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
licenseServerKeyRequest.interceptors.request.use((config) => {
|
||||
const token = getLicenseServerKeyAuthToken();
|
||||
|
||||
if (token && config.headers) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
}, (err) => {
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
licenseServerKeyRequest.interceptors.response.use((response) => {
|
||||
return response
|
||||
}, async function (err) {
|
||||
const originalRequest = err.config;
|
||||
|
||||
if (err.response.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
// refresh
|
||||
const token = await refreshLicenseServerKeyToken();
|
||||
|
||||
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
|
||||
return licenseServerKeyRequest(originalRequest);
|
||||
}
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
licenseKeyRequest.interceptors.request.use((config) => {
|
||||
const token = getLicenseKeyAuthToken();
|
||||
|
||||
if (token && config.headers) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
}, (err) => {
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
licenseKeyRequest.interceptors.response.use((response) => {
|
||||
return response
|
||||
}, async function (err) {
|
||||
const originalRequest = err.config;
|
||||
|
||||
if (err.response.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
// refresh
|
||||
const token = await refreshLicenseKeyToken();
|
||||
|
||||
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
|
||||
return licenseKeyRequest(originalRequest);
|
||||
}
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
30
backend/src/config/storage.ts
Normal file
30
backend/src/config/storage.ts
Normal file
@ -0,0 +1,30 @@
|
||||
const MemoryLicenseServerKeyTokenStorage = () => {
|
||||
let authToken: string;
|
||||
|
||||
return {
|
||||
setToken: (token: string) => {
|
||||
authToken = token;
|
||||
},
|
||||
getToken: () => authToken
|
||||
};
|
||||
};
|
||||
|
||||
const MemoryLicenseKeyTokenStorage = () => {
|
||||
let authToken: string;
|
||||
|
||||
return {
|
||||
setToken: (token: string) => {
|
||||
authToken = token;
|
||||
},
|
||||
getToken: () => authToken
|
||||
};
|
||||
};
|
||||
|
||||
const licenseServerTokenStorage = MemoryLicenseServerKeyTokenStorage();
|
||||
const licenseTokenStorage = MemoryLicenseKeyTokenStorage();
|
||||
|
||||
export const getLicenseServerKeyAuthToken = licenseServerTokenStorage.getToken;
|
||||
export const setLicenseServerKeyAuthToken = licenseServerTokenStorage.setToken;
|
||||
|
||||
export const getLicenseKeyAuthToken = licenseTokenStorage.getToken;
|
||||
export const setLicenseKeyAuthToken = licenseTokenStorage.setToken;
|
@ -5,7 +5,7 @@ import {
|
||||
IntegrationAuth,
|
||||
Bot
|
||||
} from '../../models';
|
||||
import { INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables';
|
||||
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables';
|
||||
import { IntegrationService } from '../../services';
|
||||
import {
|
||||
getApps,
|
||||
@ -16,7 +16,7 @@ import {
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL
|
||||
} from '../../variables';
|
||||
import request from '../../config/request';
|
||||
import { standardRequest } from '../../config/request';
|
||||
|
||||
/***
|
||||
* Return integration authorization with id [integrationAuthId]
|
||||
@ -129,7 +129,9 @@ export const saveIntegrationAccessToken = async (
|
||||
integration
|
||||
}, {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration
|
||||
integration,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true,
|
||||
upsert: true
|
||||
@ -229,7 +231,7 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
|
||||
let branches: string[] = [];
|
||||
|
||||
if (appId && appId !== '') {
|
||||
const { data }: { data: VercelBranch[] } = await request.get(
|
||||
const { data }: { data: VercelBranch[] } = await standardRequest.get(
|
||||
`${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`,
|
||||
{
|
||||
params,
|
||||
@ -292,7 +294,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
|
||||
projectId: appId
|
||||
}
|
||||
|
||||
const { data: { data: { environments: { edges } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||
const { data: { data: { environments: { edges } } } } = await standardRequest.post(INTEGRATION_RAILWAY_API_URL, {
|
||||
query,
|
||||
variables,
|
||||
}, {
|
||||
@ -372,7 +374,7 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
||||
id: appId
|
||||
}
|
||||
|
||||
const { data: { data: { project: { services: { edges } } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||
const { data: { data: { project: { services: { edges } } } } } = await standardRequest.post(INTEGRATION_RAILWAY_API_URL, {
|
||||
query,
|
||||
variables
|
||||
}, {
|
||||
|
@ -135,6 +135,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (!inviteeMembershipOrg) {
|
||||
|
||||
await new MembershipOrg({
|
||||
user: invitee,
|
||||
inviteEmail: inviteeEmail,
|
||||
@ -246,6 +247,10 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
||||
// membership can be approved and redirected to login/dashboard
|
||||
membershipOrg.status = ACCEPTED;
|
||||
await membershipOrg.save();
|
||||
|
||||
await updateSubscriptionOrgQuantity({
|
||||
organizationId
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully verified email',
|
||||
|
@ -19,7 +19,8 @@ export const getOrganizations = async (req: Request, res: Response) => {
|
||||
try {
|
||||
organizations = (
|
||||
await MembershipOrg.find({
|
||||
user: req.user._id
|
||||
user: req.user._id,
|
||||
status: ACCEPTED
|
||||
}).populate('organization')
|
||||
).map((m) => m.organization);
|
||||
} catch (err) {
|
||||
|
@ -21,14 +21,6 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||
try {
|
||||
email = req.body.email;
|
||||
|
||||
if (await getInviteOnlySignup()) {
|
||||
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
|
||||
const userCount = await User.countDocuments({})
|
||||
if (userCount != 0) {
|
||||
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
|
||||
}
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email }).select('+publicKey');
|
||||
if (user && user?.publicKey) {
|
||||
// case: user has already completed account
|
||||
@ -47,7 +39,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||
error: 'Failed to send email verification code'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
message: `Sent an email verification code to ${email}`
|
||||
});
|
||||
@ -74,6 +66,14 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (await getInviteOnlySignup()) {
|
||||
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
|
||||
const userCount = await User.countDocuments({})
|
||||
if (userCount != 0) {
|
||||
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
|
||||
}
|
||||
}
|
||||
|
||||
// verify email
|
||||
if (await getSmtpConfigured()) {
|
||||
await checkEmailVerification({
|
||||
|
@ -6,7 +6,7 @@ import { CreateSecretRequestBody, ModifySecretRequestBody, SanitizedSecretForCre
|
||||
const { ValidationError } = mongoose.Error;
|
||||
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
|
||||
import { AnyBulkWriteOperation } from 'mongodb';
|
||||
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
||||
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
||||
import { TelemetryService } from '../../services';
|
||||
import { User } from "../../models";
|
||||
import { AccountNotFoundError } from '../../utils/errors';
|
||||
@ -36,7 +36,9 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type: secretToCreate.type,
|
||||
user: new Types.ObjectId(req.user._id)
|
||||
user: new Types.ObjectId(req.user._id),
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
|
||||
|
||||
@ -92,7 +94,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type: rawSecret.type,
|
||||
user: new Types.ObjectId(req.user._id)
|
||||
user: new Types.ObjectId(req.user._id),
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
|
||||
sanitizedSecretesToCreate.push(safeUpdateFields)
|
||||
|
@ -1,26 +1,26 @@
|
||||
import to from 'await-to-js';
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response } from 'express';
|
||||
import { ISecret, Secret } from '../../models';
|
||||
import { ISecret, Secret, Workspace } from '../../models';
|
||||
import { IAction, SecretVersion } from '../../ee/models';
|
||||
import {
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
ACTION_DELETE_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8
|
||||
} from '../../variables';
|
||||
import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
||||
import { UnauthorizedRequestError, WorkspaceNotFoundError } from '../../utils/errors';
|
||||
import { EventService } from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { EESecretService, EELogService } from '../../ee/services';
|
||||
import { EESecretService, EELogService, EELicenseService } from '../../ee/services';
|
||||
import { TelemetryService, SecretService } from '../../services';
|
||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||
import { PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||
import Tag from '../../models/tag';
|
||||
import _, { eq } from 'lodash';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
BatchSecretRequest,
|
||||
BatchSecret
|
||||
@ -48,6 +48,12 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment: string;
|
||||
requests: BatchSecretRequest[];
|
||||
} = req.body;
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
|
||||
const isPaid = orgPlan.tier >= 1;
|
||||
|
||||
const createSecrets: BatchSecret[] = [];
|
||||
const updateSecrets: BatchSecret[] = [];
|
||||
@ -81,7 +87,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
path: fullFolderPath,
|
||||
folder: folderId,
|
||||
secretBlindIndex
|
||||
secretBlindIndex,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
break;
|
||||
case 'PATCH':
|
||||
@ -96,6 +104,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
path: fullFolderPath,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
break;
|
||||
case 'DELETE':
|
||||
@ -139,7 +149,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -196,6 +207,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext: u.secretCommentCiphertext,
|
||||
secretCommentIV: u.secretCommentIV,
|
||||
secretCommentTag: u.secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags: u.tags
|
||||
}));
|
||||
|
||||
@ -226,7 +239,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -261,7 +275,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -376,6 +391,12 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
|
||||
const isPaid = orgPlan.tier >= 1;
|
||||
|
||||
let listOfSecretsToCreate;
|
||||
if (Array.isArray(req.body.secrets)) {
|
||||
// case: create multiple secrets
|
||||
@ -444,6 +465,8 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags
|
||||
});
|
||||
})
|
||||
@ -490,7 +513,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}))
|
||||
});
|
||||
|
||||
@ -531,7 +556,8 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -595,6 +621,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
const normalizedPath = normalizePath(secretsPath as string)
|
||||
const folders = await getFoldersInDirectory(workspaceId as string, environment as string, normalizedPath)
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
|
||||
const isPaid = orgPlan.tier >= 1;
|
||||
|
||||
// secrets to return
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
@ -727,7 +759,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -831,6 +864,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
...((
|
||||
secretCommentCiphertext !== undefined &&
|
||||
@ -884,6 +919,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext: secretCommentCiphertext ? secretCommentCiphertext : secret.secretCommentCiphertext,
|
||||
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
||||
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags: tags ? tags : secret.tags
|
||||
});
|
||||
})
|
||||
@ -938,6 +975,12 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
})
|
||||
|
||||
const workspace = await Workspace.findById(key);
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
|
||||
const isPaid = orgPlan.tier >= 1;
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
@ -950,7 +993,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1072,6 +1116,12 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
});
|
||||
|
||||
const workspace = await Workspace.findById(key);
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
|
||||
const isPaid = orgPlan.tier >= 1;
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
@ -1084,7 +1134,8 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.['user-agent']
|
||||
userAgent: req.headers?.['user-agent'],
|
||||
isPaid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -7,8 +7,9 @@ import {
|
||||
} from '../../helpers/signup';
|
||||
import { issueAuthTokens } from '../../helpers/auth';
|
||||
import { INVITED, ACCEPTED } from '../../variables';
|
||||
import request from '../../config/request';
|
||||
import { standardRequest } from '../../config/request';
|
||||
import { getLoopsApiKey, getHttpsEnabled } from '../../config';
|
||||
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
||||
|
||||
/**
|
||||
* Complete setting up user by adding their personal and auth information as part of the
|
||||
@ -87,6 +88,19 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
user
|
||||
});
|
||||
|
||||
// update organization membership statuses that are
|
||||
// invited to completed with user attached
|
||||
const membershipsToUpdate = await MembershipOrg.find({
|
||||
inviteEmail: email,
|
||||
status: INVITED
|
||||
});
|
||||
|
||||
membershipsToUpdate.forEach(async (membership) => {
|
||||
await updateSubscriptionOrgQuantity({
|
||||
organizationId: membership.organization.toString()
|
||||
});
|
||||
});
|
||||
|
||||
// update organization membership statuses that are
|
||||
// invited to completed with user attached
|
||||
await MembershipOrg.updateMany(
|
||||
@ -109,7 +123,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
|
||||
// sending a welcome email to new users
|
||||
if (await getLoopsApiKey()) {
|
||||
await request.post("https://app.loops.so/api/v1/events/send", {
|
||||
await standardRequest.post("https://app.loops.so/api/v1/events/send", {
|
||||
"email": email,
|
||||
"eventName": "Sign Up",
|
||||
"firstName": firstName,
|
||||
@ -206,9 +220,20 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||
|
||||
if (!user)
|
||||
throw new Error('Failed to complete account for non-existent user');
|
||||
|
||||
|
||||
// update organization membership statuses that are
|
||||
// invited to completed with user attached
|
||||
const membershipsToUpdate = await MembershipOrg.find({
|
||||
inviteEmail: email,
|
||||
status: INVITED
|
||||
});
|
||||
|
||||
membershipsToUpdate.forEach(async (membership) => {
|
||||
await updateSubscriptionOrgQuantity({
|
||||
organizationId: membership.organization.toString()
|
||||
});
|
||||
});
|
||||
|
||||
await MembershipOrg.updateMany(
|
||||
{
|
||||
inviteEmail: email,
|
||||
|
34
backend/src/ee/controllers/v1/cloudProductsController.ts
Normal file
34
backend/src/ee/controllers/v1/cloudProductsController.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Request, Response } from 'express';
|
||||
import { EELicenseService } from '../../services';
|
||||
import { getLicenseServerUrl } from '../../../config';
|
||||
import { licenseServerKeyRequest } from '../../../config/request';
|
||||
|
||||
/**
|
||||
* Return available cloud product information.
|
||||
* Note: Nicely formatted to easily construct a table from
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getCloudProducts = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const billingCycle = req.query['billing-cycle'] as string;
|
||||
|
||||
if (EELicenseService.instanceType === 'cloud') {
|
||||
const { data } = await licenseServerKeyRequest.get(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
||||
);
|
||||
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
head: [],
|
||||
rows: []
|
||||
});
|
||||
}
|
@ -1,15 +1,19 @@
|
||||
import * as stripeController from './stripeController';
|
||||
import * as secretController from './secretController';
|
||||
import * as secretSnapshotController from './secretSnapshotController';
|
||||
import * as organizationsController from './organizationsController';
|
||||
import * as workspaceController from './workspaceController';
|
||||
import * as actionController from './actionController';
|
||||
import * as membershipController from './membershipController';
|
||||
import * as cloudProductsController from './cloudProductsController';
|
||||
|
||||
export {
|
||||
stripeController,
|
||||
secretController,
|
||||
secretSnapshotController,
|
||||
organizationsController,
|
||||
workspaceController,
|
||||
actionController,
|
||||
membershipController
|
||||
membershipController,
|
||||
cloudProductsController
|
||||
}
|
83
backend/src/ee/controllers/v1/organizationsController.ts
Normal file
83
backend/src/ee/controllers/v1/organizationsController.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { getLicenseServerUrl } from '../../../config';
|
||||
import { licenseServerKeyRequest } from '../../../config/request';
|
||||
import { EELicenseService } from '../../services';
|
||||
|
||||
/**
|
||||
* Return the organization's current plan and allowed feature set
|
||||
*/
|
||||
export const getOrganizationPlan = async (req: Request, res: Response) => {
|
||||
const { organizationId } = req.params;
|
||||
|
||||
const plan = await EELicenseService.getOrganizationPlan(organizationId);
|
||||
|
||||
return res.status(200).send({
|
||||
plan,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the organization plan to product with id [productId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateOrganizationPlan = async (req: Request, res: Response) => {
|
||||
const {
|
||||
productId
|
||||
} = req.body;
|
||||
|
||||
const { data } = await licenseServerKeyRequest.patch(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan`,
|
||||
{
|
||||
productId
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the organization's payment methods on file
|
||||
*/
|
||||
export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
|
||||
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
pmtMethods
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Stripe session URL to add payment method for organization
|
||||
*/
|
||||
export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
|
||||
const {
|
||||
success_url,
|
||||
cancel_url
|
||||
} = req.body;
|
||||
|
||||
const { data: { url } } = await licenseServerKeyRequest.post(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
|
||||
{
|
||||
success_url,
|
||||
cancel_url
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
|
||||
const { pmtMethodId } = req.params;
|
||||
|
||||
const { data } = await licenseServerKeyRequest.delete(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods/${pmtMethodId}`,
|
||||
);
|
||||
|
||||
return res.status(200).send(data);
|
||||
}
|
@ -162,6 +162,8 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
} = oldSecretVersion;
|
||||
|
||||
// update secret
|
||||
@ -182,6 +184,8 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
},
|
||||
{
|
||||
new: true
|
||||
@ -205,7 +209,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
}).save();
|
||||
|
||||
// take secret snapshot
|
||||
|
@ -93,52 +93,8 @@ const markDeletedSecretVersionsHelper = async ({
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize secret versioning by setting previously unversioned
|
||||
* secrets to version 1 and begin populating secret versions.
|
||||
*/
|
||||
const initSecretVersioningHelper = async () => {
|
||||
await Secret.updateMany(
|
||||
{ version: { $exists: false } },
|
||||
{ $set: { version: 1 } }
|
||||
);
|
||||
|
||||
const unversionedSecrets: ISecret[] = await Secret.aggregate([
|
||||
{
|
||||
$lookup: {
|
||||
from: "secretversions",
|
||||
localField: "_id",
|
||||
foreignField: "secret",
|
||||
as: "versions",
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
versions: { $size: 0 },
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
if (unversionedSecrets.length > 0) {
|
||||
await addSecretVersionsHelper({
|
||||
secretVersions: unversionedSecrets.map(
|
||||
(s, idx) =>
|
||||
new SecretVersion({
|
||||
...s,
|
||||
secret: s._id,
|
||||
version: s.version ? s.version : 1,
|
||||
isDeleted: false,
|
||||
workspace: s.workspace,
|
||||
environment: s.environment,
|
||||
})
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
takeSecretSnapshotHelper,
|
||||
addSecretVersionsHelper,
|
||||
markDeletedSecretVersionsHelper,
|
||||
initSecretVersioningHelper,
|
||||
markDeletedSecretVersionsHelper
|
||||
};
|
||||
|
@ -2,6 +2,9 @@ import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../../variables';
|
||||
|
||||
export interface ISecretVersion {
|
||||
@ -20,6 +23,8 @@ export interface ISecretVersion {
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
algorithm: 'aes-256-gcm';
|
||||
keyEncoding: 'utf8' | 'base64';
|
||||
}
|
||||
|
||||
const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
@ -85,7 +90,20 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
secretValueTag: {
|
||||
type: String, // symmetric
|
||||
required: true
|
||||
}
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
],
|
||||
required: true
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
|
20
backend/src/ee/routes/v1/cloudProducts.ts
Normal file
20
backend/src/ee/routes/v1/cloudProducts.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
validateRequest
|
||||
} from '../../../middleware';
|
||||
import { query } from 'express-validator';
|
||||
import { cloudProductsController } from '../../controllers/v1';
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
}),
|
||||
query('billing-cycle').exists().isIn(['monthly', 'yearly']),
|
||||
validateRequest,
|
||||
cloudProductsController.getCloudProducts
|
||||
);
|
||||
|
||||
export default router;
|
@ -1,11 +1,15 @@
|
||||
import secret from './secret';
|
||||
import secretSnapshot from './secretSnapshot';
|
||||
import organizations from './organizations';
|
||||
import workspace from './workspace';
|
||||
import action from './action';
|
||||
import cloudProducts from './cloudProducts';
|
||||
|
||||
export {
|
||||
secret,
|
||||
secretSnapshot,
|
||||
organizations,
|
||||
workspace,
|
||||
action
|
||||
action,
|
||||
cloudProducts
|
||||
}
|
87
backend/src/ee/routes/v1/organizations.ts
Normal file
87
backend/src/ee/routes/v1/organizations.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireOrganizationAuth,
|
||||
validateRequest
|
||||
} from '../../../middleware';
|
||||
import { param, body } from 'express-validator';
|
||||
import { organizationsController } from '../../controllers/v1';
|
||||
import {
|
||||
OWNER, ADMIN, MEMBER, ACCEPTED
|
||||
} from '../../../variables';
|
||||
|
||||
router.get(
|
||||
'/:organizationId/plan',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
param('organizationId').exists().trim(),
|
||||
validateRequest,
|
||||
organizationsController.getOrganizationPlan
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:organizationId/plan',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
param('organizationId').exists().trim(),
|
||||
body('productId').exists().isString(),
|
||||
validateRequest,
|
||||
organizationsController.updateOrganizationPlan
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:organizationId/billing-details/payment-methods',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
param('organizationId').exists().trim(),
|
||||
validateRequest,
|
||||
organizationsController.getOrganizationPmtMethods
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:organizationId/billing-details/payment-methods',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
param('organizationId').exists().trim(),
|
||||
body('success_url').exists().isString(),
|
||||
body('cancel_url').exists().isString(),
|
||||
validateRequest,
|
||||
organizationsController.addOrganizationPmtMethod
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:organizationId/billing-details/payment-methods/:pmtMethodId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt', 'apiKey']
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED]
|
||||
}),
|
||||
param('organizationId').exists().trim(),
|
||||
validateRequest,
|
||||
organizationsController.deleteOrganizationPmtMethod
|
||||
);
|
||||
|
||||
export default router;
|
@ -7,7 +7,7 @@ import {
|
||||
requireAuth,
|
||||
validateRequest
|
||||
} from '../../../middleware';
|
||||
import { param, body } from 'express-validator';
|
||||
import { param } from 'express-validator';
|
||||
import { ADMIN, MEMBER } from '../../../variables';
|
||||
import { secretSnapshotController } from '../../controllers/v1';
|
||||
|
||||
|
@ -1,12 +1,133 @@
|
||||
import NodeCache from 'node-cache';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
getLicenseKey,
|
||||
getLicenseServerKey,
|
||||
getLicenseServerUrl
|
||||
} from '../../config';
|
||||
import {
|
||||
licenseKeyRequest,
|
||||
licenseServerKeyRequest,
|
||||
refreshLicenseServerKeyToken,
|
||||
refreshLicenseKeyToken
|
||||
} from '../../config/request';
|
||||
import { Organization } from '../../models';
|
||||
import { OrganizationNotFoundError } from '../../utils/errors';
|
||||
|
||||
interface FeatureSet {
|
||||
_id: string | null;
|
||||
slug: 'starter' | 'team' | 'pro' | 'enterprise' | null;
|
||||
tier: number;
|
||||
workspaceLimit: number | null;
|
||||
workspacesUsed: number;
|
||||
memberLimit: number | null;
|
||||
membersUsed: number;
|
||||
secretVersioning: boolean;
|
||||
pitRecovery: boolean;
|
||||
rbac: boolean;
|
||||
customRateLimits: boolean;
|
||||
customAlerts: boolean;
|
||||
auditLogs: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to handle Enterprise Edition license actions
|
||||
* Class to handle license/plan configurations:
|
||||
* - Infisical Cloud: Fetch and cache customer plans in [localFeatureSet]
|
||||
* - Self-hosted regular: Use default global feature set
|
||||
* - Self-hosted enterprise: Fetch and update global feature set
|
||||
*/
|
||||
class EELicenseService {
|
||||
|
||||
private readonly _isLicenseValid: boolean;
|
||||
private readonly _isLicenseValid: boolean; // TODO: deprecate
|
||||
|
||||
public instanceType: 'self-hosted' | 'enterprise-self-hosted' | 'cloud' = 'self-hosted';
|
||||
|
||||
public globalFeatureSet: FeatureSet = {
|
||||
_id: null,
|
||||
slug: null,
|
||||
tier: -1,
|
||||
workspaceLimit: null,
|
||||
workspacesUsed: 0,
|
||||
memberLimit: null,
|
||||
membersUsed: 0,
|
||||
secretVersioning: true,
|
||||
pitRecovery: true,
|
||||
rbac: true,
|
||||
customRateLimits: true,
|
||||
customAlerts: true,
|
||||
auditLogs: false
|
||||
}
|
||||
|
||||
public localFeatureSet: NodeCache;
|
||||
|
||||
constructor(licenseKey: string) {
|
||||
constructor() {
|
||||
this._isLicenseValid = true;
|
||||
this.localFeatureSet = new NodeCache({
|
||||
stdTTL: 300
|
||||
});
|
||||
}
|
||||
|
||||
public async getOrganizationPlan(organizationId: string): Promise<FeatureSet> {
|
||||
try {
|
||||
if (this.instanceType === 'cloud') {
|
||||
const cachedPlan = this.localFeatureSet.get<FeatureSet>(organizationId);
|
||||
if (cachedPlan) {
|
||||
return cachedPlan;
|
||||
}
|
||||
|
||||
const organization = await Organization.findById(organizationId);
|
||||
if (!organization) throw OrganizationNotFoundError();
|
||||
|
||||
const { data: { currentPlan } } = await licenseServerKeyRequest.get(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`
|
||||
);
|
||||
|
||||
// cache fetched plan for organization
|
||||
this.localFeatureSet.set(organizationId, currentPlan);
|
||||
|
||||
return currentPlan;
|
||||
}
|
||||
} catch (err) {
|
||||
return this.globalFeatureSet;
|
||||
}
|
||||
|
||||
return this.globalFeatureSet;
|
||||
}
|
||||
|
||||
public async initGlobalFeatureSet() {
|
||||
const licenseServerKey = await getLicenseServerKey();
|
||||
const licenseKey = await getLicenseKey();
|
||||
|
||||
try {
|
||||
if (licenseServerKey) {
|
||||
// license server key is present -> validate it
|
||||
const token = await refreshLicenseServerKeyToken()
|
||||
|
||||
if (token) {
|
||||
this.instanceType = 'cloud';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (licenseKey) {
|
||||
// license key is present -> validate it
|
||||
const token = await refreshLicenseKeyToken();
|
||||
|
||||
if (token) {
|
||||
const { data: { currentPlan } } = await licenseKeyRequest.get(
|
||||
`${await getLicenseServerUrl()}/api/license/v1/plan`
|
||||
);
|
||||
|
||||
this.globalFeatureSet = currentPlan;
|
||||
this.instanceType = 'enterprise-self-hosted';
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// case: self-hosted free
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
}
|
||||
|
||||
public get isLicenseValid(): boolean {
|
||||
@ -14,4 +135,4 @@ class EELicenseService {
|
||||
}
|
||||
}
|
||||
|
||||
export default new EELicenseService('N/A');
|
||||
export default new EELicenseService();
|
@ -3,8 +3,7 @@ import { ISecretVersion } from '../models';
|
||||
import {
|
||||
takeSecretSnapshotHelper,
|
||||
addSecretVersionsHelper,
|
||||
markDeletedSecretVersionsHelper,
|
||||
initSecretVersioningHelper
|
||||
markDeletedSecretVersionsHelper
|
||||
} from '../helpers/secret';
|
||||
import EELicenseService from './EELicenseService';
|
||||
|
||||
@ -64,15 +63,6 @@ class EESecretService {
|
||||
secretIds
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize secret versioning by setting previously unversioned
|
||||
* secrets to version 1 and begin populating secret versions.
|
||||
*/
|
||||
static async initSecretVersioning() {
|
||||
if (!EELicenseService.isLicenseValid) return;
|
||||
await initSecretVersioningHelper();
|
||||
}
|
||||
}
|
||||
|
||||
export default EESecretService;
|
@ -41,7 +41,6 @@ const validateAuthMode = ({
|
||||
headers: { [key: string]: string | string[] | undefined },
|
||||
acceptedAuthModes: string[]
|
||||
}) => {
|
||||
// TODO: refactor middleware
|
||||
const apiKey = headers['x-api-key'];
|
||||
const authHeader = headers['authorization'];
|
||||
|
||||
|
@ -4,107 +4,26 @@ import {
|
||||
BotKey,
|
||||
Secret,
|
||||
ISecret,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
IUser
|
||||
} from "../models";
|
||||
import {
|
||||
generateKeyPair,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric,
|
||||
decryptAsymmetric,
|
||||
} from "../utils/crypto";
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
decryptSymmetric128BitHexKeyUTF8,
|
||||
decryptAsymmetric
|
||||
} from '../utils/crypto';
|
||||
import {
|
||||
SECRET_SHARED,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from "../variables";
|
||||
import { getEncryptionKey } from "../config";
|
||||
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { validateMembership } from "../helpers/membership";
|
||||
import { validateUserClientForWorkspace } from "../helpers/user";
|
||||
import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for bot with id [botId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.botId - id of bot to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
*/
|
||||
const validateClientForBot = async ({
|
||||
authData,
|
||||
botId,
|
||||
acceptedRoles,
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
botId: Types.ObjectId;
|
||||
acceptedRoles: Array<"admin" | "member">;
|
||||
}) => {
|
||||
const bot = await Bot.findById(botId);
|
||||
|
||||
if (!bot) throw BotNotFoundError();
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_JWT &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: bot.workspace,
|
||||
acceptedRoles,
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||
authData.authPayload instanceof ServiceAccount
|
||||
) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: bot.workspace,
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
||||
authData.authPayload instanceof ServiceTokenData
|
||||
) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for bot",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_API_KEY &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: bot.workspace,
|
||||
acceptedRoles,
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
throw BotNotFoundError({
|
||||
message: "Failed client authorization for bot",
|
||||
});
|
||||
};
|
||||
import {
|
||||
getEncryptionKey,
|
||||
getRootEncryptionKey,
|
||||
client
|
||||
} from "../config";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
|
||||
/**
|
||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||
@ -119,23 +38,52 @@ const createBot = async ({
|
||||
name: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const { publicKey, privateKey } = generateKeyPair();
|
||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||
plaintext: privateKey,
|
||||
key: await getEncryptionKey(),
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||
|
||||
return await new Bot({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
isActive: false,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
}).save();
|
||||
|
||||
} else if (encryptionKey) {
|
||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: privateKey,
|
||||
key: await getEncryptionKey(),
|
||||
});
|
||||
|
||||
return await new Bot({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
isActive: false,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to create new bot due to missing encryption key'
|
||||
});
|
||||
|
||||
const bot = await new Bot({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
isActive: false,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
}).save();
|
||||
|
||||
return bot;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -161,14 +109,14 @@ const getSecretsHelper = async ({
|
||||
});
|
||||
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
const secretKey = decryptSymmetric({
|
||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretKeyCiphertext,
|
||||
iv: secret.secretKeyIV,
|
||||
tag: secret.secretKeyTag,
|
||||
key,
|
||||
});
|
||||
|
||||
const secretValue = decryptSymmetric({
|
||||
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretValueCiphertext,
|
||||
iv: secret.secretValueIV,
|
||||
tag: secret.secretValueTag,
|
||||
@ -189,34 +137,54 @@ const getSecretsHelper = async ({
|
||||
* @returns {String} key - decrypted workspace key
|
||||
*/
|
||||
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const botKey = await BotKey.findOne({
|
||||
workspace: workspaceId,
|
||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
||||
})
|
||||
.populate<{ sender: IUser }>("sender", "publicKey");
|
||||
|
||||
if (!botKey) throw new Error("Failed to find bot key");
|
||||
|
||||
const bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
}).select("+encryptedPrivateKey +iv +tag");
|
||||
}).select("+encryptedPrivateKey +iv +tag +algorithm +keyEncoding");
|
||||
|
||||
if (!bot) throw new Error("Failed to find bot");
|
||||
if (!bot.isActive) throw new Error("Bot is not active");
|
||||
|
||||
const privateKeyBot = decryptSymmetric({
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: await getEncryptionKey(),
|
||||
});
|
||||
if (rootEncryptionKey && bot.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
// case: encoding scheme is base64
|
||||
const privateKeyBot = client.decryptSymmetric(bot.encryptedPrivateKey, rootEncryptionKey, bot.iv, bot.tag);
|
||||
|
||||
const key = decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
publicKey: botKey.sender.publicKey as string,
|
||||
privateKey: privateKeyBot,
|
||||
});
|
||||
return decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
publicKey: botKey.sender.publicKey as string,
|
||||
privateKey: privateKeyBot,
|
||||
});
|
||||
} else if (encryptionKey && bot.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
|
||||
// case: encoding scheme is utf8
|
||||
const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return decryptAsymmetric({
|
||||
ciphertext: botKey.encryptedKey,
|
||||
nonce: botKey.nonce,
|
||||
publicKey: botKey.sender.publicKey as string,
|
||||
privateKey: privateKeyBot,
|
||||
});
|
||||
}
|
||||
|
||||
return key;
|
||||
throw InternalServerError({
|
||||
message: "Failed to obtain bot's copy of workspace key needed for bot operations"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -234,7 +202,7 @@ const encryptSymmetricHelper = async ({
|
||||
plaintext: string;
|
||||
}) => {
|
||||
const key = await getKey({ workspaceId: workspaceId.toString() });
|
||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext,
|
||||
key,
|
||||
});
|
||||
@ -266,7 +234,7 @@ const decryptSymmetricHelper = async ({
|
||||
tag: string;
|
||||
}) => {
|
||||
const key = await getKey({ workspaceId: workspaceId.toString() });
|
||||
const plaintext = decryptSymmetric({
|
||||
const plaintext = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
@ -277,9 +245,8 @@ const decryptSymmetricHelper = async ({
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForBot,
|
||||
createBot,
|
||||
getSecretsHelper,
|
||||
encryptSymmetricHelper,
|
||||
decryptSymmetricHelper,
|
||||
decryptSymmetricHelper
|
||||
};
|
||||
|
@ -1,6 +1,4 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { EESecretService } from '../ee/services';
|
||||
import { SecretService } from '../services';
|
||||
import { getLogger } from '../utils/logger';
|
||||
|
||||
/**
|
||||
@ -21,9 +19,7 @@ const initDatabaseHelper = async ({
|
||||
mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');
|
||||
|
||||
(await getLogger("database")).info("Database connection established");
|
||||
|
||||
await EESecretService.initSecretVersioning();
|
||||
await SecretService.initSecretBlindIndexDataHelper();
|
||||
|
||||
} catch (err) {
|
||||
(await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`);
|
||||
}
|
||||
|
@ -3,40 +3,20 @@ import { Types } from 'mongoose';
|
||||
import {
|
||||
Bot,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
IntegrationAuth
|
||||
} from '../models';
|
||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||
import { BotService } from '../services';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY
|
||||
INTEGRATION_NETLIFY,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8
|
||||
} from '../variables';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
IntegrationAuthNotFoundError,
|
||||
IntegrationNotFoundError
|
||||
} from '../utils/errors';
|
||||
import RequestError from '../utils/requestError';
|
||||
import {
|
||||
validateClientForIntegrationAuth
|
||||
} from '../helpers/integrationAuth';
|
||||
import {
|
||||
validateUserClientForWorkspace
|
||||
} from '../helpers/user';
|
||||
import {
|
||||
validateServiceAccountClientForWorkspace
|
||||
} from '../helpers/serviceAccount';
|
||||
import { IntegrationService } from '../services';
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
@ -45,84 +25,6 @@ interface Update {
|
||||
accountId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for integration with id [integrationId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.integrationId - id of integration to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForIntegration = async ({
|
||||
authData,
|
||||
integrationId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
integrationId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
|
||||
const integration = await Integration.findById(integrationId);
|
||||
if (!integration) throw IntegrationNotFoundError();
|
||||
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integration.integrationAuth)
|
||||
.select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id
|
||||
})).accessToken;
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integration.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: integration.workspace
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for integration'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integration.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for integration'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
||||
* named [integration]
|
||||
@ -400,7 +302,9 @@ const setIntegrationAuthRefreshHelper = async ({
|
||||
}, {
|
||||
refreshCiphertext: obj.ciphertext,
|
||||
refreshIV: obj.iv,
|
||||
refreshTag: obj.tag
|
||||
refreshTag: obj.tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true
|
||||
});
|
||||
@ -461,7 +365,9 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessExpiresAt
|
||||
accessExpiresAt,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}, {
|
||||
new: true
|
||||
});
|
||||
@ -475,7 +381,6 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForIntegration,
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
getIntegrationAuthRefreshHelper,
|
||||
|
@ -2,105 +2,12 @@ import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Membership,
|
||||
Key,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
Key
|
||||
} from '../models';
|
||||
import {
|
||||
MembershipNotFoundError,
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError
|
||||
BadRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import {
|
||||
validateUserClientForWorkspace
|
||||
} from '../helpers/user';
|
||||
import {
|
||||
validateServiceAccountClientForWorkspace
|
||||
} from '../helpers/serviceAccount';
|
||||
import {
|
||||
validateServiceTokenDataClientForWorkspace
|
||||
} from '../helpers/serviceTokenData';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for membership with id [membershipId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.membershipId - id of membership to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspaceRoles
|
||||
* @returns {Membership} - validated membership
|
||||
*/
|
||||
const validateClientForMembership = async ({
|
||||
authData,
|
||||
membershipId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
membershipId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
|
||||
const membership = await Membership.findById(membershipId);
|
||||
|
||||
if (!membership) throw MembershipNotFoundError({
|
||||
message: 'Failed to find membership'
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: membership.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: membership.workspace
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId: new Types.ObjectId(membership.workspace)
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: membership.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for membership'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
||||
@ -230,7 +137,6 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForMembership,
|
||||
validateMembership,
|
||||
addMemberships,
|
||||
findMembership,
|
||||
|
@ -3,95 +3,12 @@ import {
|
||||
MembershipOrg,
|
||||
Workspace,
|
||||
Membership,
|
||||
Key,
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
Key
|
||||
} from '../models';
|
||||
import {
|
||||
MembershipOrgNotFoundError,
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.membershipOrgId - id of organization membership to validate against
|
||||
* @param {Array<'owner' | 'admin' | 'member'>} obj.acceptedRoles - accepted organization roles
|
||||
* @param {MembershipOrg} - validated organization membership
|
||||
*/
|
||||
const validateClientForMembershipOrg = async ({
|
||||
authData,
|
||||
membershipOrgId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
membershipOrgId: Types.ObjectId;
|
||||
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||
}) => {
|
||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||
|
||||
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||
message: 'Failed to find organization membership '
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({
|
||||
message: 'Failed service account client authorization for organization membership'
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service account client authorization for organization membership'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for organization membership'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
||||
@ -234,7 +151,6 @@ const deleteMembershipOrg = async ({
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForMembershipOrg,
|
||||
validateMembershipOrg,
|
||||
findMembershipOrg,
|
||||
addMembershipsOrg,
|
||||
|
@ -1,21 +1,8 @@
|
||||
import Stripe from "stripe";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IUser,
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData,
|
||||
ServiceTokenData,
|
||||
} from "../models";
|
||||
import { Organization, MembershipOrg } from "../models";
|
||||
import {
|
||||
ACCEPTED,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
OWNER,
|
||||
ACCEPTED
|
||||
} from "../variables";
|
||||
import {
|
||||
getStripeSecretKey,
|
||||
@ -24,93 +11,15 @@ import {
|
||||
getStripeProductStarter,
|
||||
} from "../config";
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
OrganizationNotFoundError,
|
||||
} from "../utils/errors";
|
||||
import { validateUserClientForOrganization } from "../helpers/user";
|
||||
import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
|
||||
|
||||
/**
|
||||
* Validate accepted clients for organization with id [organizationId]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.organizationId - id of organization to validate against
|
||||
*/
|
||||
const validateClientForOrganization = async ({
|
||||
authData,
|
||||
organizationId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
organizationId: Types.ObjectId;
|
||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||
acceptedStatuses: Array<"invited" | "accepted">;
|
||||
}) => {
|
||||
const organization = await Organization.findById(organizationId);
|
||||
|
||||
if (!organization) {
|
||||
throw OrganizationNotFoundError({
|
||||
message: "Failed to find organization",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_JWT &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
const membershipOrg = await validateUserClientForOrganization({
|
||||
user: authData.authPayload,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
});
|
||||
|
||||
return { organization, membershipOrg };
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||
authData.authPayload instanceof ServiceAccount
|
||||
) {
|
||||
await validateServiceAccountClientForOrganization({
|
||||
serviceAccount: authData.authPayload,
|
||||
organization,
|
||||
});
|
||||
|
||||
return { organization };
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
||||
authData.authPayload instanceof ServiceTokenData
|
||||
) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for organization",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_API_KEY &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
const membershipOrg = await validateUserClientForOrganization({
|
||||
user: authData.authPayload,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
});
|
||||
|
||||
return { organization, membershipOrg };
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed client authorization for organization",
|
||||
});
|
||||
};
|
||||
EELicenseService
|
||||
} from '../ee/services';
|
||||
import {
|
||||
getLicenseServerUrl
|
||||
} from '../config';
|
||||
import {
|
||||
licenseServerKeyRequest,
|
||||
licenseKeyRequest
|
||||
} from '../config/request';
|
||||
|
||||
/**
|
||||
* Create an organization with name [name]
|
||||
@ -228,38 +137,44 @@ const updateSubscriptionOrgQuantity = async ({
|
||||
});
|
||||
|
||||
if (organization && organization.customerId) {
|
||||
const quantity = await MembershipOrg.countDocuments({
|
||||
organization: organizationId,
|
||||
status: ACCEPTED,
|
||||
});
|
||||
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: "2022-08-01",
|
||||
});
|
||||
|
||||
const subscription = (
|
||||
await stripe.subscriptions.list({
|
||||
customer: organization.customerId,
|
||||
})
|
||||
).data[0];
|
||||
|
||||
stripeSubscription = await stripe.subscriptions.update(subscription.id, {
|
||||
items: [
|
||||
if (EELicenseService.instanceType === 'cloud') {
|
||||
// instance of Infisical is a cloud instance
|
||||
const quantity = await MembershipOrg.countDocuments({
|
||||
organization: new Types.ObjectId(organizationId),
|
||||
status: ACCEPTED,
|
||||
});
|
||||
|
||||
await licenseServerKeyRequest.patch(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`,
|
||||
{
|
||||
id: subscription.items.data[0].id,
|
||||
price: subscription.items.data[0].price.id,
|
||||
quantity,
|
||||
},
|
||||
],
|
||||
});
|
||||
quantity
|
||||
}
|
||||
);
|
||||
|
||||
EELicenseService.localFeatureSet.del(organizationId);
|
||||
}
|
||||
|
||||
if (EELicenseService.instanceType === 'enterprise-self-hosted') {
|
||||
// instance of Infisical is an enterprise self-hosted instance
|
||||
|
||||
const usedSeats = await MembershipOrg.countDocuments({
|
||||
status: ACCEPTED
|
||||
});
|
||||
|
||||
await licenseKeyRequest.patch(
|
||||
`${await getLicenseServerUrl()}/api/license/v1/license`,
|
||||
{
|
||||
usedSeats
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return stripeSubscription;
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForOrganization,
|
||||
createOrganization,
|
||||
initSubscriptionOrg,
|
||||
updateSubscriptionOrgQuantity,
|
||||
updateSubscriptionOrgQuantity
|
||||
};
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
} from "../variables";
|
||||
import _ from "lodash";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
@ -194,6 +196,8 @@ const v1PushSecrets = async ({
|
||||
secretValueIV: newSecret.ivValue,
|
||||
secretValueTag: newSecret.tagValue,
|
||||
secretValueHash: newSecret.hashValue,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
}),
|
||||
});
|
||||
@ -225,6 +229,8 @@ const v1PushSecrets = async ({
|
||||
secretCommentIV: s.ivComment,
|
||||
secretCommentTag: s.tagComment,
|
||||
secretCommentHash: s.hashComment,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
};
|
||||
|
||||
if (toAdd[idx].type === "personal") {
|
||||
@ -254,6 +260,8 @@ const v1PushSecrets = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
}) =>
|
||||
new SecretVersion({
|
||||
secret: _id,
|
||||
@ -271,6 +279,8 @@ const v1PushSecrets = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash,
|
||||
algorithm,
|
||||
keyEncoding
|
||||
})
|
||||
),
|
||||
});
|
||||
@ -467,6 +477,8 @@ const v2PushSecrets = async ({
|
||||
workspace: workspaceId,
|
||||
type: toAdd[idx].type,
|
||||
environment,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(toAdd[idx].type === "personal" ? { user: userId } : {}),
|
||||
}))
|
||||
);
|
||||
@ -478,6 +490,8 @@ const v2PushSecrets = async ({
|
||||
...secretDocument,
|
||||
secret: secretDocument._id,
|
||||
isDeleted: false,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
@ -7,58 +7,35 @@ import {
|
||||
DeleteSecretParams
|
||||
} from '../interfaces/services/SecretService';
|
||||
import {
|
||||
AuthData
|
||||
} from '../interfaces/middleware';
|
||||
import {
|
||||
User,
|
||||
Workspace,
|
||||
ServiceAccount,
|
||||
ServiceTokenData,
|
||||
Secret,
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
} from '../models';
|
||||
import { SecretVersion } from '../ee/models';
|
||||
import {
|
||||
validateMembership
|
||||
} from '../helpers/membership';
|
||||
import {
|
||||
validateUserClientForSecret,
|
||||
validateUserClientForSecrets
|
||||
} from '../helpers/user';
|
||||
import {
|
||||
validateServiceTokenDataClientForSecrets,
|
||||
validateServiceTokenDataClientForWorkspace
|
||||
} from '../helpers/serviceTokenData';
|
||||
import {
|
||||
validateServiceAccountClientForSecrets,
|
||||
validateServiceAccountClientForWorkspace
|
||||
} from '../helpers/serviceAccount';
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
SecretNotFoundError,
|
||||
SecretBlindIndexDataNotFoundError
|
||||
SecretBlindIndexDataNotFoundError,
|
||||
InternalServerError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
ACTION_DELETE_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../variables';
|
||||
import crypto from 'crypto';
|
||||
import * as argon2 from 'argon2';
|
||||
import {
|
||||
encryptSymmetric,
|
||||
decryptSymmetric
|
||||
import {
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
decryptSymmetric128BitHexKeyUTF8
|
||||
} from '../utils/crypto';
|
||||
import { getEncryptionKey } from '../config';
|
||||
import { getEncryptionKey, client, getRootEncryptionKey } from '../config';
|
||||
import { TelemetryService } from '../services';
|
||||
import {
|
||||
EESecretService,
|
||||
@ -69,199 +46,6 @@ import {
|
||||
getAuthDataPayloadUserObj
|
||||
} from '../utils/auth';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for secrets with id [secretId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForSecret = async ({
|
||||
authData,
|
||||
secretId,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: AuthData;
|
||||
secretId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
const secret = await Secret.findById(secretId);
|
||||
|
||||
if (!secret) throw SecretNotFoundError({
|
||||
message: 'Failed to find secret'
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecret({
|
||||
user: authData.authPayload,
|
||||
secret,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: secret.workspace,
|
||||
environment: secret.environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId: secret.workspace,
|
||||
environment: secret.environment
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecret({
|
||||
user: authData.authPayload,
|
||||
secret,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for secret'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for secrets with ids [secretIds] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId[]} obj.secretIds - id of workspace to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForSecrets = async ({
|
||||
authData,
|
||||
secretIds,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: AuthData;
|
||||
secretIds: Types.ObjectId[];
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
secrets = await Secret.find({
|
||||
_id: {
|
||||
$in: secretIds
|
||||
}
|
||||
});
|
||||
|
||||
if (secrets.length != secretIds.length) {
|
||||
throw BadRequestError({ message: 'Failed to validate non-existent secrets' })
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecrets({
|
||||
user: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForSecrets({
|
||||
serviceAccount: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForSecrets({
|
||||
serviceTokenData: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecrets({
|
||||
user: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for secrets resource'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize secret blind index data by setting previously
|
||||
* un-initialized projects to have secret blind index data
|
||||
* (Ensures that all projects have associated blind index data)
|
||||
*/
|
||||
const initSecretBlindIndexDataHelper = async () => {
|
||||
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace');
|
||||
const workspaceIdsToBlindIndex = await Workspace.distinct('_id', {
|
||||
_id: {
|
||||
$nin: workspaceIdsBlindIndexed
|
||||
}
|
||||
});
|
||||
|
||||
const secretBlindIndexDataToInsert = await Promise.all(
|
||||
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric({
|
||||
plaintext: salt,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
const secretBlindIndexData = new SecretBlindIndexData({
|
||||
workspace: workspaceToBlindIndex,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag
|
||||
})
|
||||
|
||||
return secretBlindIndexData;
|
||||
})
|
||||
);
|
||||
|
||||
if (secretBlindIndexDataToInsert.length > 0) {
|
||||
await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index [salt]
|
||||
* for workspace with id [workspaceId]
|
||||
@ -273,26 +57,47 @@ const createSecretBlindIndexDataHelper = async ({
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
|
||||
// initialize random blind index salt for workspace
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric({
|
||||
plaintext: salt,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
const secretBlindIndexData = await new SecretBlindIndexData({
|
||||
workspace: workspaceId,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag
|
||||
}).save();
|
||||
|
||||
return secretBlindIndexData;
|
||||
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||
|
||||
return await new SecretBlindIndexData({
|
||||
workspace: workspaceId,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
}).save();
|
||||
} else {
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: salt,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return await new SecretBlindIndexData({
|
||||
workspace: workspaceId,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -306,22 +111,36 @@ const getSecretBlindIndexSaltHelper = async ({
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
// check if workspace blind index data exists
|
||||
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId
|
||||
});
|
||||
}).select('+algorithm +keyEncoding');
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
// decrypt workspace salt
|
||||
const salt = decryptSymmetric({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: await getEncryptionKey()
|
||||
|
||||
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
return client.decryptSymmetric(
|
||||
secretBlindIndexData.encryptedSaltCiphertext,
|
||||
rootEncryptionKey,
|
||||
secretBlindIndexData.saltIV,
|
||||
secretBlindIndexData.saltTag
|
||||
);
|
||||
} else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
// decrypt workspace salt
|
||||
return decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: encryptionKey
|
||||
});
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to obtain workspace salt needed for secret blind indexing'
|
||||
});
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,7 +195,7 @@ const generateSecretBlindIndexHelper = async ({
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
// decrypt workspace salt
|
||||
const salt = decryptSymmetric({
|
||||
const salt = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
@ -464,7 +283,9 @@ const createSecretHelper = async ({
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
|
||||
const secretVersion = new SecretVersion({
|
||||
@ -481,7 +302,9 @@ const createSecretHelper = async ({
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
|
||||
// // (EE) add version for new secret
|
||||
@ -771,7 +594,9 @@ const updateSecretHelper = async ({
|
||||
secretKeyTag: secret.secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
|
||||
// (EE) add version for new secret
|
||||
@ -932,9 +757,6 @@ const deleteSecretHelper = async ({
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForSecret,
|
||||
validateClientForSecrets,
|
||||
initSecretBlindIndexDataHelper,
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
|
@ -1,24 +1,8 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
ISecret,
|
||||
IServiceAccount,
|
||||
User,
|
||||
Membership,
|
||||
IOrganization,
|
||||
Organization,
|
||||
} from '../models';
|
||||
import { sendMail } from './nodemailer';
|
||||
import { validateMembership } from './membership';
|
||||
import _ from 'lodash';
|
||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||
import {
|
||||
validateMembershipOrg
|
||||
} from '../helpers/membershipOrg';
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Initialize a user under email [email]
|
||||
@ -26,7 +10,7 @@ import {
|
||||
* @param {String} obj.email - email of user to initialize
|
||||
* @returns {Object} user - the initialized user
|
||||
*/
|
||||
const setupAccount = async ({ email }: { email: string }) => {
|
||||
export const setupAccount = async ({ email }: { email: string }) => {
|
||||
const user = await new User({
|
||||
email
|
||||
}).save();
|
||||
@ -52,7 +36,7 @@ const setupAccount = async ({ email }: { email: string }) => {
|
||||
* @param {String} obj.verifier - verifier for auth SRP
|
||||
* @returns {Object} user - the completed user
|
||||
*/
|
||||
const completeAccount = async ({
|
||||
export const completeAccount = async ({
|
||||
userId,
|
||||
firstName,
|
||||
lastName,
|
||||
@ -113,7 +97,7 @@ const completeAccount = async ({
|
||||
* @param {String} obj.ip - login ip address
|
||||
* @param {String} obj.userAgent - login user-agent
|
||||
*/
|
||||
const checkUserDevice = async ({
|
||||
export const checkUserDevice = async ({
|
||||
user,
|
||||
ip,
|
||||
userAgent
|
||||
@ -148,206 +132,4 @@ const checkUserDevice = async ({
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access workspace
|
||||
* with id [workspaceId] and its environment [environment] with required permissions
|
||||
* [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||
* @param {String} environment - (optional) environment in workspace to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateUserClientForWorkspace = async ({
|
||||
user,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
// validate user membership in workspace
|
||||
const membership = await validateMembership({
|
||||
userId: user._id,
|
||||
workspaceId,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
let runningIsDisallowed = false;
|
||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||
switch (requiredPermission) {
|
||||
case PERMISSION_READ_SECRETS:
|
||||
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||
break;
|
||||
case PERMISSION_WRITE_SECRETS:
|
||||
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (runningIsDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access secret [secret]
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateUserClientForSecret = async ({
|
||||
user,
|
||||
secret,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
secret: ISecret;
|
||||
acceptedRoles?: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
const membership = await validateMembership({
|
||||
userId: user._id,
|
||||
workspaceId: secret.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||
|
||||
if (isDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'You do not have the required permissions to perform this action'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access secrets [secrets]
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateUserClientForSecrets = async ({
|
||||
user,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
secrets: ISecret[];
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
// TODO: add acceptedRoles?
|
||||
|
||||
const userMemberships = await Membership.find({ user: user._id })
|
||||
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
||||
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
|
||||
|
||||
// for each secret check if the secret belongs to a workspace the user is a member of
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
if (!workspaceIdsSet.has(secret.workspace.toString())) {
|
||||
throw BadRequestError({
|
||||
message: 'Failed authorization for the secret'
|
||||
});
|
||||
}
|
||||
|
||||
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||
|
||||
if (isDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'You do not have the required permissions to perform this action'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access service account [serviceAccount]
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateUserClientForServiceAccount = async ({
|
||||
user,
|
||||
serviceAccount,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
serviceAccount: IServiceAccount;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
if (!serviceAccount.user.equals(user._id)) {
|
||||
// case: user who created service account is not the
|
||||
// same user that is on the request
|
||||
await validateMembershipOrg({
|
||||
userId: user._id,
|
||||
organizationId: serviceAccount.organization,
|
||||
acceptedRoles: [],
|
||||
acceptedStatuses: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access organization [organization]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Organization} obj.organization - organization to validate against
|
||||
*/
|
||||
const validateUserClientForOrganization = async ({
|
||||
user,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
user: IUser;
|
||||
organization: IOrganization;
|
||||
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||
}) => {
|
||||
const membershipOrg = await validateMembershipOrg({
|
||||
userId: user._id,
|
||||
organizationId: organization._id,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
export {
|
||||
setupAccount,
|
||||
completeAccount,
|
||||
checkUserDevice,
|
||||
validateUserClientForWorkspace,
|
||||
validateUserClientForSecrets,
|
||||
validateUserClientForServiceAccount,
|
||||
validateUserClientForOrganization,
|
||||
validateUserClientForSecret
|
||||
};
|
||||
}
|
@ -1,136 +1,14 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import crypto from 'crypto';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Workspace,
|
||||
Bot,
|
||||
Membership,
|
||||
Key,
|
||||
Secret,
|
||||
User,
|
||||
IUser,
|
||||
ServiceAccountWorkspacePermission,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
SecretBlindIndexData
|
||||
Secret
|
||||
} from '../models';
|
||||
import { createBot } from '../helpers/bot';
|
||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||
import { validateServiceTokenDataClientForWorkspace } from '../helpers/serviceTokenData';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { UnauthorizedRequestError, WorkspaceNotFoundError } from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import { encryptSymmetric } from '../utils/crypto';
|
||||
import { SecretService } from '../services';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateClientForWorkspace = async ({
|
||||
authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled: boolean;
|
||||
}) => {
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
|
||||
if (!workspace) throw WorkspaceNotFoundError({
|
||||
message: 'Failed to find workspace'
|
||||
});
|
||||
|
||||
if (requireBlindIndicesEnabled) {
|
||||
// case: blind indices are not enabled for secrets in this workspace
|
||||
// (i.e. workspace was created before blind indices were introduced
|
||||
// and no admin has enabled it)
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!secretBlindIndexData) throw UnauthorizedRequestError({
|
||||
message: 'Failed workspace authorization due to blind indices not being enabled'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
const membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return ({ membership });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
const membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return ({ membership });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for workspace'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a workspace with name [name] in organization with id [organizationId]
|
||||
* and a bot for it.
|
||||
@ -203,7 +81,6 @@ const deleteWorkspace = async ({ id }: { id: string }) => {
|
||||
};
|
||||
|
||||
export {
|
||||
validateClientForWorkspace,
|
||||
createWorkspace,
|
||||
deleteWorkspace
|
||||
};
|
||||
|
@ -1,19 +1,11 @@
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { DatabaseService } from './services';
|
||||
import { EELicenseService } from './ee/services';
|
||||
import { setUpHealthEndpoint } from './services/health';
|
||||
import { initSmtp } from './services/smtp';
|
||||
import { TelemetryService } from './services';
|
||||
import { setTransporter } from './helpers/nodemailer';
|
||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { patchRouterParam } = require('./utils/patchAsyncRoutes');
|
||||
|
||||
import cookieParser from 'cookie-parser';
|
||||
import swaggerUi = require('swagger-ui-express');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@ -25,7 +17,9 @@ import {
|
||||
workspace as eeWorkspaceRouter,
|
||||
secret as eeSecretRouter,
|
||||
secretSnapshot as eeSecretSnapshotRouter,
|
||||
action as eeActionRouter
|
||||
action as eeActionRouter,
|
||||
organizations as eeOrganizationsRouter,
|
||||
cloudProducts as eeCloudProductsRouter
|
||||
} from './ee/routes/v1';
|
||||
import {
|
||||
signup as v1SignupRouter,
|
||||
@ -70,29 +64,17 @@ import { getLogger } from './utils/logger';
|
||||
import { RouteNotFoundError } from './utils/errors';
|
||||
import { requestErrorHandler } from './middleware/requestErrorHandler';
|
||||
import {
|
||||
getMongoURL,
|
||||
getNodeEnv,
|
||||
getPort,
|
||||
getSentryDSN,
|
||||
getSiteURL,
|
||||
getSmtpHost
|
||||
getSiteURL
|
||||
} from './config';
|
||||
import { setup } from './utils/setup';
|
||||
|
||||
const main = async () => {
|
||||
TelemetryService.logTelemetryMessage();
|
||||
setTransporter(await initSmtp());
|
||||
await setup();
|
||||
|
||||
await DatabaseService.initDatabase(await getMongoURL());
|
||||
if ((await getNodeEnv()) !== 'test') {
|
||||
Sentry.init({
|
||||
dsn: await getSentryDSN(),
|
||||
tracesSampleRate: 1.0,
|
||||
debug: await getNodeEnv() === 'production' ? false : true,
|
||||
environment: await getNodeEnv()
|
||||
});
|
||||
}
|
||||
await EELicenseService.initGlobalFeatureSet();
|
||||
|
||||
patchRouterParam();
|
||||
const app = express();
|
||||
app.enable('trust proxy');
|
||||
app.use(express.json());
|
||||
@ -119,6 +101,8 @@ const main = async () => {
|
||||
app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter);
|
||||
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
||||
app.use('/api/v1/action', eeActionRouter);
|
||||
app.use('/api/v1/organizations', eeOrganizationsRouter);
|
||||
app.use('/api/v1/cloud-products', eeCloudProductsRouter);
|
||||
|
||||
// v1 routes (default)
|
||||
app.use('/api/v1/signup', v1SignupRouter);
|
||||
@ -132,8 +116,8 @@ const main = async () => {
|
||||
app.use('/api/v1/membership', v1MembershipRouter);
|
||||
app.use('/api/v1/key', v1KeyRouter);
|
||||
app.use('/api/v1/invite-org', v1InviteOrgRouter);
|
||||
app.use('/api/v1/secret', v1SecretRouter);
|
||||
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated
|
||||
app.use('/api/v1/secret', v1SecretRouter); // deprecate
|
||||
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecate
|
||||
app.use('/api/v1/password', v1PasswordRouter);
|
||||
app.use('/api/v1/stripe', v1StripeRouter);
|
||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||
@ -148,9 +132,9 @@ const main = async () => {
|
||||
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
||||
app.use('/api/v2/workspace', v2TagsRouter);
|
||||
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
||||
app.use('/api/v2/secret', v2SecretRouter); // deprecate
|
||||
app.use('/api/v2/secrets', v2SecretsRouter); // note: in the process of moving to v3/secrets
|
||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter);
|
||||
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
||||
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
||||
|
||||
@ -161,7 +145,7 @@ const main = async () => {
|
||||
// api docs
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
||||
|
||||
// Server status
|
||||
// server status
|
||||
app.use('/api', healthCheck)
|
||||
|
||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||
@ -176,7 +160,7 @@ const main = async () => {
|
||||
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
|
||||
});
|
||||
|
||||
await createTestUserForDevelopment();
|
||||
// await createTestUserForDevelopment();
|
||||
setUpHealthEndpoint(server);
|
||||
|
||||
server.on('close', async () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import request from "../config/request";
|
||||
import { standardRequest } from "../config/request";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
@ -134,7 +134,7 @@ const getApps = async ({
|
||||
*/
|
||||
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
||||
const res = (
|
||||
await request.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
|
||||
await standardRequest.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.heroku+json; version=3",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
@ -164,7 +164,7 @@ const getAppsVercel = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const res = (
|
||||
await request.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
|
||||
await standardRequest.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
@ -208,7 +208,7 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
|
||||
filter: 'all'
|
||||
});
|
||||
|
||||
const { data } = await request.get(
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`,
|
||||
{
|
||||
params,
|
||||
@ -310,7 +310,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
||||
*/
|
||||
const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
|
||||
const res = (
|
||||
await request.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
|
||||
await standardRequest.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
@ -358,7 +358,7 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
|
||||
projects: { edges },
|
||||
},
|
||||
},
|
||||
} = await request.post(
|
||||
} = await standardRequest.post(
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
{
|
||||
query,
|
||||
@ -402,7 +402,7 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||
`;
|
||||
|
||||
const res = (
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
{
|
||||
query,
|
||||
@ -436,7 +436,7 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
||||
*/
|
||||
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
||||
const res = (
|
||||
await request.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, {
|
||||
await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, {
|
||||
headers: {
|
||||
"Circle-Token": accessToken,
|
||||
"Accept-Encoding": "application/json",
|
||||
@ -455,7 +455,7 @@ const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
||||
|
||||
const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
|
||||
const res = (
|
||||
await request.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, {
|
||||
await standardRequest.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, {
|
||||
headers: {
|
||||
Authorization: `token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
@ -502,7 +502,7 @@ const getAppsGitlab = async ({
|
||||
per_page: String(perPage),
|
||||
});
|
||||
|
||||
const { data } = await request.get(
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
|
||||
{
|
||||
params,
|
||||
@ -530,7 +530,7 @@ const getAppsGitlab = async ({
|
||||
// case: fetch projects for individual in GitLab
|
||||
|
||||
const { id } = (
|
||||
await request.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, {
|
||||
await standardRequest.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
@ -544,7 +544,7 @@ const getAppsGitlab = async ({
|
||||
per_page: String(perPage),
|
||||
});
|
||||
|
||||
const { data } = await request.get(
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
|
||||
{
|
||||
params,
|
||||
@ -581,7 +581,7 @@ const getAppsGitlab = async ({
|
||||
* @returns {String} apps.name - name of Supabase app
|
||||
*/
|
||||
const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
|
||||
const { data } = await request.get(
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
|
||||
{
|
||||
headers: {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import request from "../config/request";
|
||||
import { standardRequest } from "../config/request";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
@ -142,7 +142,7 @@ const exchangeCodeAzure = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
|
||||
const res: ExchangeCodeAzureResponse = (
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
@ -178,7 +178,7 @@ const exchangeCodeHeroku = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
|
||||
const res: ExchangeCodeHerokuResponse = (
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
@ -209,7 +209,7 @@ const exchangeCodeHeroku = async ({ code }: { code: string }) => {
|
||||
*/
|
||||
const exchangeCodeVercel = async ({ code }: { code: string }) => {
|
||||
const res: ExchangeCodeVercelResponse = (
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
code: code,
|
||||
@ -240,7 +240,7 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
|
||||
*/
|
||||
const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
||||
const res: ExchangeCodeNetlifyResponse = (
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
@ -252,14 +252,14 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
||||
)
|
||||
).data;
|
||||
|
||||
const res2 = await request.get("https://api.netlify.com/api/v1/sites", {
|
||||
const res2 = await standardRequest.get("https://api.netlify.com/api/v1/sites", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${res.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const res3 = (
|
||||
await request.get("https://api.netlify.com/api/v1/accounts", {
|
||||
await standardRequest.get("https://api.netlify.com/api/v1/accounts", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${res.access_token}`,
|
||||
},
|
||||
@ -287,7 +287,7 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
||||
*/
|
||||
const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||
const res: ExchangeCodeGithubResponse = (
|
||||
await request.get(INTEGRATION_GITHUB_TOKEN_URL, {
|
||||
await standardRequest.get(INTEGRATION_GITHUB_TOKEN_URL, {
|
||||
params: {
|
||||
client_id: await getClientIdGitHub(),
|
||||
client_secret: await getClientSecretGitHub(),
|
||||
@ -321,7 +321,7 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const res: ExchangeCodeGitlabResponse = (
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import request from "../config/request";
|
||||
import { standardRequest } from "../config/request";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
@ -121,7 +121,7 @@ const exchangeRefreshAzure = async ({
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
|
||||
const { data }: { data: RefreshTokenAzureResponse } = await standardRequest.post(
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
client_id: await getClientIdAzure(),
|
||||
@ -158,7 +158,7 @@ const exchangeRefreshHeroku = async ({
|
||||
data,
|
||||
}: {
|
||||
data: RefreshTokenHerokuResponse;
|
||||
} = await request.post(
|
||||
} = await standardRequest.post(
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
@ -193,7 +193,7 @@ const exchangeRefreshGitLab = async ({
|
||||
data,
|
||||
}: {
|
||||
data: RefreshTokenGitLabResponse;
|
||||
} = await request.post(
|
||||
} = await standardRequest.post(
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
|
@ -37,8 +37,7 @@ import {
|
||||
INTEGRATION_TRAVISCI_API_URL,
|
||||
INTEGRATION_SUPABASE_API_URL
|
||||
} from "../variables";
|
||||
import request from '../config/request';
|
||||
import axios from "axios";
|
||||
import { standardRequest} from '../config/request';
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to [app] in integration named [integration]
|
||||
@ -215,7 +214,7 @@ const syncSecretsAzureKeyVault = async ({
|
||||
let result: GetAzureKeyVaultSecret[] = [];
|
||||
try {
|
||||
while (url) {
|
||||
const res = await request.get(url, {
|
||||
const res = await standardRequest.get(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
@ -242,7 +241,7 @@ const syncSecretsAzureKeyVault = async ({
|
||||
lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf('/');
|
||||
}
|
||||
|
||||
const azureKeyVaultSecret = await request.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
|
||||
const azureKeyVaultSecret = await standardRequest.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
@ -308,7 +307,7 @@ const syncSecretsAzureKeyVault = async ({
|
||||
while (!isSecretSet && maxTries > 0) {
|
||||
// try to set secret
|
||||
try {
|
||||
await request.put(
|
||||
await standardRequest.put(
|
||||
`${integration.app}/secrets/${key}?api-version=7.3`,
|
||||
{
|
||||
value
|
||||
@ -325,7 +324,7 @@ const syncSecretsAzureKeyVault = async ({
|
||||
} catch (err) {
|
||||
const error: any = err;
|
||||
if (error?.response?.data?.error?.innererror?.code === 'ObjectIsDeletedButRecoverable') {
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${integration.app}/deletedsecrets/${key}/recover?api-version=7.3`, {},
|
||||
{
|
||||
headers: {
|
||||
@ -355,7 +354,7 @@ const syncSecretsAzureKeyVault = async ({
|
||||
|
||||
for await (const deleteSecret of deleteSecrets) {
|
||||
const { key } = deleteSecret;
|
||||
await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
|
||||
await standardRequest.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
@ -568,7 +567,7 @@ const syncSecretsHeroku = async ({
|
||||
}) => {
|
||||
try {
|
||||
const herokuSecrets = (
|
||||
await request.get(
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||
{
|
||||
headers: {
|
||||
@ -586,7 +585,7 @@ const syncSecretsHeroku = async ({
|
||||
}
|
||||
});
|
||||
|
||||
await request.patch(
|
||||
await standardRequest.patch(
|
||||
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||
secrets,
|
||||
{
|
||||
@ -642,7 +641,7 @@ const syncSecretsVercel = async ({
|
||||
: {}),
|
||||
};
|
||||
|
||||
const vercelSecrets: VercelSecret[] = (await request.get(
|
||||
const vercelSecrets: VercelSecret[] = (await standardRequest.get(
|
||||
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
|
||||
{
|
||||
params,
|
||||
@ -675,7 +674,7 @@ const syncSecretsVercel = async ({
|
||||
for await (const vercelSecret of vercelSecrets) {
|
||||
if (vercelSecret.type === 'encrypted') {
|
||||
// case: secret is encrypted -> need to decrypt
|
||||
const decryptedSecret = (await request.get(
|
||||
const decryptedSecret = (await standardRequest.get(
|
||||
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`,
|
||||
{
|
||||
params,
|
||||
@ -747,7 +746,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
// Sync/push new secrets
|
||||
if (newSecrets.length > 0) {
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`,
|
||||
newSecrets,
|
||||
{
|
||||
@ -763,7 +762,7 @@ const syncSecretsVercel = async ({
|
||||
for await (const secret of updateSecrets) {
|
||||
if (secret.type !== 'sensitive') {
|
||||
const { id, ...updatedSecret } = secret;
|
||||
await request.patch(
|
||||
await standardRequest.patch(
|
||||
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
|
||||
updatedSecret,
|
||||
{
|
||||
@ -778,7 +777,7 @@ const syncSecretsVercel = async ({
|
||||
}
|
||||
|
||||
for await (const secret of deleteSecrets) {
|
||||
await request.delete(
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
|
||||
{
|
||||
params,
|
||||
@ -837,7 +836,7 @@ const syncSecretsNetlify = async ({
|
||||
});
|
||||
|
||||
const res = (
|
||||
await request.get(
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
|
||||
{
|
||||
params: getParams,
|
||||
@ -951,7 +950,7 @@ const syncSecretsNetlify = async ({
|
||||
});
|
||||
|
||||
if (newSecrets.length > 0) {
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
|
||||
newSecrets,
|
||||
{
|
||||
@ -966,7 +965,7 @@ const syncSecretsNetlify = async ({
|
||||
|
||||
if (updateSecrets.length > 0) {
|
||||
updateSecrets.forEach(async (secret: NetlifySecret) => {
|
||||
await request.patch(
|
||||
await standardRequest.patch(
|
||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
|
||||
{
|
||||
context: secret.values[0].context,
|
||||
@ -985,7 +984,7 @@ const syncSecretsNetlify = async ({
|
||||
|
||||
if (deleteSecrets.length > 0) {
|
||||
deleteSecrets.forEach(async (key: string) => {
|
||||
await request.delete(
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`,
|
||||
{
|
||||
params: syncParams,
|
||||
@ -1000,7 +999,7 @@ const syncSecretsNetlify = async ({
|
||||
|
||||
if (deleteSecretValues.length > 0) {
|
||||
deleteSecretValues.forEach(async (secret: NetlifySecret) => {
|
||||
await request.delete(
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`,
|
||||
{
|
||||
params: syncParams,
|
||||
@ -1151,7 +1150,7 @@ const syncSecretsRender = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
await request.put(
|
||||
await standardRequest.put(
|
||||
`${INTEGRATION_RENDER_API_URL}/v1/services/${integration.appId}/env-vars`,
|
||||
Object.keys(secrets).map((key) => ({
|
||||
key,
|
||||
@ -1203,7 +1202,7 @@ const syncSecretsRailway = async ({
|
||||
variables: secrets
|
||||
};
|
||||
|
||||
await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||
await standardRequest.post(INTEGRATION_RAILWAY_API_URL, {
|
||||
query,
|
||||
variables: {
|
||||
input,
|
||||
@ -1261,7 +1260,7 @@ const syncSecretsFlyio = async ({
|
||||
}
|
||||
`;
|
||||
|
||||
await request.post(INTEGRATION_FLYIO_API_URL, {
|
||||
await standardRequest.post(INTEGRATION_FLYIO_API_URL, {
|
||||
query: SetSecrets,
|
||||
variables: {
|
||||
input: {
|
||||
@ -1296,7 +1295,7 @@ const syncSecretsFlyio = async ({
|
||||
}
|
||||
}`;
|
||||
|
||||
const getSecretsRes = (await request.post(INTEGRATION_FLYIO_API_URL, {
|
||||
const getSecretsRes = (await standardRequest.post(INTEGRATION_FLYIO_API_URL, {
|
||||
query: GetSecrets,
|
||||
variables: {
|
||||
appName: integration.app,
|
||||
@ -1332,7 +1331,7 @@ const syncSecretsFlyio = async ({
|
||||
}
|
||||
}`;
|
||||
|
||||
await request.post(INTEGRATION_FLYIO_API_URL, {
|
||||
await standardRequest.post(INTEGRATION_FLYIO_API_URL, {
|
||||
query: DeleteSecrets,
|
||||
variables: {
|
||||
input: {
|
||||
@ -1373,7 +1372,7 @@ const syncSecretsCircleCI = async ({
|
||||
}) => {
|
||||
try {
|
||||
const circleciOrganizationDetail = (
|
||||
await request.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, {
|
||||
await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, {
|
||||
headers: {
|
||||
"Circle-Token": accessToken,
|
||||
"Accept-Encoding": "application/json",
|
||||
@ -1386,7 +1385,7 @@ const syncSecretsCircleCI = async ({
|
||||
// sync secrets to CircleCI
|
||||
Object.keys(secrets).forEach(
|
||||
async (key) =>
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
||||
{
|
||||
name: key,
|
||||
@ -1403,7 +1402,7 @@ const syncSecretsCircleCI = async ({
|
||||
|
||||
// get secrets from CircleCI
|
||||
const getSecretsRes = (
|
||||
await request.get(
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
||||
{
|
||||
headers: {
|
||||
@ -1417,7 +1416,7 @@ const syncSecretsCircleCI = async ({
|
||||
// delete secrets from CircleCI
|
||||
getSecretsRes.forEach(async (sec: any) => {
|
||||
if (!(sec.name in secrets)) {
|
||||
await request.delete(
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`,
|
||||
{
|
||||
headers: {
|
||||
@ -1454,7 +1453,7 @@ const syncSecretsTravisCI = async ({
|
||||
try {
|
||||
// get secrets from travis-ci
|
||||
const getSecretsRes = (
|
||||
await request.get(
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`,
|
||||
{
|
||||
headers: {
|
||||
@ -1476,7 +1475,7 @@ const syncSecretsTravisCI = async ({
|
||||
if (!(key in getSecretsRes)) {
|
||||
// case: secret does not exist in travis ci
|
||||
// -> add secret
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`,
|
||||
{
|
||||
env_var: {
|
||||
@ -1495,7 +1494,7 @@ const syncSecretsTravisCI = async ({
|
||||
} else {
|
||||
// case: secret exists in travis ci
|
||||
// -> update/set secret
|
||||
await request.patch(
|
||||
await standardRequest.patch(
|
||||
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`,
|
||||
{
|
||||
env_var: {
|
||||
@ -1517,7 +1516,7 @@ const syncSecretsTravisCI = async ({
|
||||
for await (const key of Object.keys(getSecretsRes)) {
|
||||
if (!(key in secrets)){
|
||||
// delete secret
|
||||
await request.delete(
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`,
|
||||
{
|
||||
headers: {
|
||||
@ -1554,9 +1553,15 @@ const syncSecretsGitLab = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
interface GitLabSecret {
|
||||
key: string;
|
||||
value: string;
|
||||
environment_scope: string;
|
||||
}
|
||||
|
||||
// get secrets from gitlab
|
||||
const getSecretsRes = (
|
||||
await request.get(
|
||||
const getSecretsRes: GitLabSecret[] = (
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
|
||||
{
|
||||
headers: {
|
||||
@ -1565,12 +1570,16 @@ const syncSecretsGitLab = async ({
|
||||
},
|
||||
}
|
||||
)
|
||||
).data;
|
||||
)
|
||||
.data
|
||||
.filter((secret: GitLabSecret) =>
|
||||
secret.environment_scope === integration.targetEnvironment
|
||||
);
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
||||
if (!existingSecret) {
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
|
||||
{
|
||||
key: key,
|
||||
@ -1578,7 +1587,7 @@ const syncSecretsGitLab = async ({
|
||||
protected: false,
|
||||
masked: false,
|
||||
raw: false,
|
||||
environment_scope:'*'
|
||||
environment_scope: integration.targetEnvironment
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
@ -1589,29 +1598,31 @@ const syncSecretsGitLab = async ({
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// udpate secret
|
||||
await request.put(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
|
||||
{
|
||||
...existingSecret,
|
||||
value: secrets[existingSecret.key]
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "application/json",
|
||||
// update secret
|
||||
if (secrets[key] !== existingSecret.value) {
|
||||
await standardRequest.put(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
...existingSecret,
|
||||
value: secrets[existingSecret.key]
|
||||
},
|
||||
}
|
||||
)
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete secrets
|
||||
for await (const sec of getSecretsRes) {
|
||||
if (!(sec.key in secrets)) {
|
||||
await request.delete(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
@ -1620,7 +1631,7 @@ const syncSecretsGitLab = async ({
|
||||
);
|
||||
}
|
||||
}
|
||||
}catch (err) {
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
throw new Error("Failed to sync secrets to GitLab");
|
||||
@ -1645,7 +1656,7 @@ const syncSecretsSupabase = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
try {
|
||||
const { data: getSecretsRes } = await request.get(
|
||||
const { data: getSecretsRes } = await standardRequest.get(
|
||||
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
|
||||
{
|
||||
headers: {
|
||||
@ -1665,7 +1676,7 @@ const syncSecretsSupabase = async ({
|
||||
}
|
||||
);
|
||||
|
||||
await request.post(
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
|
||||
modifiedFormatForSecretInjection,
|
||||
{
|
||||
@ -1683,7 +1694,7 @@ const syncSecretsSupabase = async ({
|
||||
}
|
||||
});
|
||||
|
||||
await request.delete(
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
|
||||
{
|
||||
headers: {
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_GITLAB_API_URL
|
||||
} from '../variables';
|
||||
import request from '../config/request';
|
||||
import { standardRequest } from '../config/request';
|
||||
|
||||
interface Team {
|
||||
name: string;
|
||||
@ -56,7 +56,7 @@ const getTeamsGitLab = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
let teams: Team[] = [];
|
||||
const res = (await request.get(
|
||||
const res = (await standardRequest.get(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
||||
{
|
||||
headers: {
|
||||
|
41
backend/src/interfaces/utils/crypto.ts
Normal file
41
backend/src/interfaces/utils/crypto.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export interface IGenerateKeyPairOutput {
|
||||
publicKey: string;
|
||||
privateKey: string
|
||||
}
|
||||
|
||||
export interface IEncryptAsymmetricInput {
|
||||
plaintext: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
export interface IEncryptAsymmetricOutput {
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export interface IDecryptAsymmetricInput {
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
export interface IEncryptSymmetricInput {
|
||||
plaintext: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface IEncryptSymmetricOutput {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
export interface IDecryptSymmetricInput {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
key: string;
|
||||
}
|
||||
|
1
backend/src/interfaces/utils/index.ts
Normal file
1
backend/src/interfaces/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './crypto';
|
@ -7,9 +7,6 @@ import {
|
||||
getAuthAPIKeyPayload,
|
||||
getAuthSAAKPayload
|
||||
} from '../helpers/auth';
|
||||
import {
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
@ -48,6 +45,7 @@ const requireAuth = ({
|
||||
|
||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||
// and return token type [authTokenType] and value [authTokenValue]
|
||||
|
||||
const { authMode, authTokenValue } = validateAuthMode({
|
||||
headers: req.headers,
|
||||
acceptedAuthModes
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { Bot } from '../models';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { validateClientForBot } from '../helpers/bot';
|
||||
import { AccountNotFoundError } from '../utils/errors';
|
||||
import { validateClientForBot } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { Integration, IntegrationAuth } from '../models';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { validateClientForIntegration } from '../helpers/integration';
|
||||
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||
import { validateClientForIntegration } from '../validation';
|
||||
|
||||
/**
|
||||
* Validate if user on request is a member of workspace with proper roles associated
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { IntegrationAuth, IWorkspace } from '../models';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateClientForIntegrationAuth } from '../helpers/integrationAuth';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import { validateClientForIntegrationAuth } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import {
|
||||
Membership,
|
||||
} from '../models';
|
||||
import {
|
||||
validateClientForMembership,
|
||||
validateMembership
|
||||
} from '../helpers/membership';
|
||||
import { validateClientForMembership } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,16 +1,6 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import {
|
||||
MembershipOrg
|
||||
} from '../models';
|
||||
import {
|
||||
validateClientForMembershipOrg,
|
||||
validateMembershipOrg
|
||||
} from '../helpers/membershipOrg';
|
||||
|
||||
|
||||
// TODO: transform
|
||||
import { validateClientForMembershipOrg } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { IOrganization, MembershipOrg } from '../models';
|
||||
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
||||
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
||||
import { validateClientForOrganization } from '../helpers/organization';
|
||||
import { validateClientForOrganization } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { UnauthorizedRequestError, SecretNotFoundError } from '../utils/errors';
|
||||
import { Secret } from '../models';
|
||||
import {
|
||||
validateMembership
|
||||
} from '../helpers/membership';
|
||||
import {
|
||||
validateClientForSecret
|
||||
} from '../helpers/secrets';
|
||||
import { validateClientForSecret } from '../validation';
|
||||
|
||||
// note: used for old /v1/secret and /v2/secret routes.
|
||||
// newer /v2/secrets routes use [requireSecretsAuth] middleware with the exception
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import { Secret, Membership } from '../models';
|
||||
import { validateClientForSecrets } from '../helpers/secrets';
|
||||
import { validateClientForSecrets } from '../validation';
|
||||
|
||||
const requireSecretsAuth = ({
|
||||
acceptedRoles,
|
||||
|
@ -1,15 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { ServiceAccount } from '../models';
|
||||
import {
|
||||
ServiceAccountNotFoundError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
validateMembershipOrg
|
||||
} from '../helpers/membershipOrg';
|
||||
import {
|
||||
validateClientForServiceAccount
|
||||
} from '../helpers/serviceAccount';
|
||||
import { validateClientForServiceAccount } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { ServiceToken, ServiceTokenData } from '../models';
|
||||
import { validateClientForServiceTokenData } from '../helpers/serviceTokenData';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||
import { validateClientForServiceTokenData } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import { validateClientForWorkspace } from '../helpers/workspace';
|
||||
import { UnauthorizedRequestError } from '../utils/errors';
|
||||
import { validateClientForWorkspace } from '../validation';
|
||||
|
||||
type req = 'params' | 'body' | 'query';
|
||||
|
||||
@ -31,7 +29,7 @@ const requireWorkspaceAuth = ({
|
||||
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||
|
||||
// validate clients
|
||||
const { membership } = await validateClientForWorkspace({
|
||||
const { membership, workspace } = await validateClientForWorkspace({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
@ -43,6 +41,10 @@ const requireWorkspaceAuth = ({
|
||||
if (membership) {
|
||||
req.membership = membership;
|
||||
}
|
||||
|
||||
if (workspace) {
|
||||
req.workspace = workspace;
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../variables';
|
||||
|
||||
export interface IBackupPrivateKey {
|
||||
_id: Types.ObjectId;
|
||||
@ -7,6 +12,8 @@ export interface IBackupPrivateKey {
|
||||
iv: string;
|
||||
tag: string;
|
||||
salt: string;
|
||||
algorithm: string;
|
||||
keyEncoding: 'base64' | 'utf8';
|
||||
verifier: string;
|
||||
}
|
||||
|
||||
@ -32,6 +39,19 @@ const backupPrivateKeySchema = new Schema<IBackupPrivateKey>(
|
||||
select: false,
|
||||
required: true
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
],
|
||||
required: true
|
||||
},
|
||||
salt: {
|
||||
type: String,
|
||||
select: false,
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_HEX,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../variables';
|
||||
|
||||
export interface IBot {
|
||||
_id: Types.ObjectId;
|
||||
@ -9,6 +15,8 @@ export interface IBot {
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
algorithm: 'aes-256-gcm';
|
||||
keyEncoding: 'base64' | 'utf8';
|
||||
}
|
||||
|
||||
const botSchema = new Schema<IBot>(
|
||||
@ -45,6 +53,21 @@ const botSchema = new Schema<IBot>(
|
||||
type: String,
|
||||
required: true,
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ export interface IIntegration {
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
isActive: boolean;
|
||||
url: string;
|
||||
app: string;
|
||||
appId: string;
|
||||
owner: string;
|
||||
@ -63,6 +64,11 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
url: {
|
||||
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
app: {
|
||||
// name of app in provider
|
||||
type: String,
|
||||
|
@ -14,6 +14,9 @@ import {
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from "../variables";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
@ -31,6 +34,8 @@ export interface IIntegrationAuth extends Document {
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: 'aes-256-gcm';
|
||||
keyEncoding?: 'utf8' | 'base64';
|
||||
accessExpiresAt?: Date;
|
||||
}
|
||||
|
||||
@ -109,6 +114,19 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
type: Date,
|
||||
select: false,
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
@ -2,6 +2,9 @@ import { Schema, model, Types } from 'mongoose';
|
||||
import {
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../variables';
|
||||
import { ROOT_FOLDER_PATH } from '../utils/folder';
|
||||
|
||||
@ -25,6 +28,8 @@ export interface ISecret {
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretCommentHash?: string;
|
||||
algorithm: 'aes-256-gcm';
|
||||
keyEncoding: 'utf8' | 'base64';
|
||||
tags?: string[];
|
||||
path?: string;
|
||||
folder?: Types.ObjectId;
|
||||
@ -111,6 +116,19 @@ const secretSchema = new Schema<ISecret>(
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
],
|
||||
required: true
|
||||
},
|
||||
// the full path to the secret in relation to folders
|
||||
path: {
|
||||
type: String,
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { Schema, model, Types, Document } from 'mongoose';
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../variables';
|
||||
|
||||
export interface ISecretBlindIndexData extends Document {
|
||||
_id: Types.ObjectId;
|
||||
@ -6,6 +11,8 @@ export interface ISecretBlindIndexData extends Document {
|
||||
encryptedSaltCiphertext: string;
|
||||
saltIV: string;
|
||||
saltTag: string;
|
||||
algorithm: 'aes-256-gcm';
|
||||
keyEncoding: 'base64' | 'utf8'
|
||||
}
|
||||
|
||||
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
||||
@ -15,7 +22,7 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
||||
ref: 'Workspace',
|
||||
required: true
|
||||
},
|
||||
encryptedSaltCiphertext: {
|
||||
encryptedSaltCiphertext: { // TODO: make these select: false
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
@ -26,7 +33,23 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
||||
saltTag: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
algorithm: {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -7,9 +7,9 @@ import {
|
||||
requireSecretsAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { validateClientForSecrets } from '../../validation';
|
||||
import { query, body } from 'express-validator';
|
||||
import { secretsController } from '../../controllers/v2';
|
||||
import { validateClientForSecrets } from '../../helpers/secrets';
|
||||
import {
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
|
@ -1,4 +1,3 @@
|
||||
// WIP
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
ISecret
|
||||
@ -11,7 +10,6 @@ import {
|
||||
DeleteSecretParams
|
||||
} from '../interfaces/services/SecretService';
|
||||
import {
|
||||
initSecretBlindIndexDataHelper,
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
@ -24,16 +22,6 @@ import {
|
||||
} from '../helpers/secrets';
|
||||
|
||||
class SecretService {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param param0 h
|
||||
* @returns
|
||||
*/
|
||||
|
||||
static async initSecretBlindIndexDataHelper() {
|
||||
return await initSecretBlindIndexDataHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index salt
|
||||
|
@ -5,6 +5,7 @@
|
||||
************************************************************************************************/
|
||||
|
||||
import { Key, Membership, MembershipOrg, Organization, User, Workspace } from "../models";
|
||||
import { SecretService } from "../services";
|
||||
import { Types } from 'mongoose';
|
||||
import { getNodeEnv } from '../config';
|
||||
|
||||
@ -119,7 +120,12 @@ export const createTestUserForDevelopment = async () => {
|
||||
// create workspace if not exist
|
||||
const workspaceInDB = await Workspace.findById(testWorkspaceId)
|
||||
if (!workspaceInDB) {
|
||||
await Workspace.create(testWorkspace)
|
||||
const workspace = await Workspace.create(testWorkspace)
|
||||
|
||||
// initialize blind index salt for workspace
|
||||
await SecretService.createSecretBlindIndexData({
|
||||
workspaceId: workspace._id
|
||||
});
|
||||
}
|
||||
|
||||
// create workspace key if not exist
|
||||
|
@ -1,139 +0,0 @@
|
||||
import nacl from 'tweetnacl';
|
||||
import util from 'tweetnacl-util';
|
||||
import AesGCM from './aes-gcm';
|
||||
|
||||
/**
|
||||
* Return new base64, NaCl, public-private key pair.
|
||||
* @returns {Object} obj
|
||||
* @returns {String} obj.publicKey - base64, NaCl, public key
|
||||
* @returns {String} obj.privateKey - base64, NaCl, private key
|
||||
*/
|
||||
const generateKeyPair = () => {
|
||||
const pair = nacl.box.keyPair();
|
||||
|
||||
return ({
|
||||
publicKey: util.encodeBase64(pair.publicKey),
|
||||
privateKey: util.encodeBase64(pair.secretKey)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return assymmetrically encrypted [plaintext] using [publicKey] where
|
||||
* [publicKey] likely belongs to the recipient.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
* @param {String} obj.publicKey - public key of the recipient
|
||||
* @param {String} obj.privateKey - private key of the sender (current user)
|
||||
* @returns {Object} obj
|
||||
* @returns {String} ciphertext - base64-encoded ciphertext
|
||||
* @returns {String} nonce - base64-encoded nonce
|
||||
*/
|
||||
const encryptAsymmetric = ({
|
||||
plaintext,
|
||||
publicKey,
|
||||
privateKey
|
||||
}: {
|
||||
plaintext: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}) => {
|
||||
const nonce = nacl.randomBytes(24);
|
||||
const ciphertext = nacl.box(
|
||||
util.decodeUTF8(plaintext),
|
||||
nonce,
|
||||
util.decodeBase64(publicKey),
|
||||
util.decodeBase64(privateKey)
|
||||
);
|
||||
|
||||
return {
|
||||
ciphertext: util.encodeBase64(ciphertext),
|
||||
nonce: util.encodeBase64(nonce)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
|
||||
* [privateKey] likely belongs to the recipient.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.nonce - nonce
|
||||
* @param {String} obj.publicKey - public key of the sender
|
||||
* @param {String} obj.privateKey - private key of the receiver (current user)
|
||||
* @param {String} plaintext - UTF8 plaintext
|
||||
*/
|
||||
const decryptAsymmetric = ({
|
||||
ciphertext,
|
||||
nonce,
|
||||
publicKey,
|
||||
privateKey
|
||||
}: {
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}): string => {
|
||||
const plaintext: any = nacl.box.open(
|
||||
util.decodeBase64(ciphertext),
|
||||
util.decodeBase64(nonce),
|
||||
util.decodeBase64(publicKey),
|
||||
util.decodeBase64(privateKey)
|
||||
);
|
||||
|
||||
return util.encodeUTF8(plaintext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return symmetrically encrypted [plaintext] using [key].
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
* @param {String} obj.key - hex key
|
||||
*/
|
||||
const encryptSymmetric = ({
|
||||
plaintext,
|
||||
key
|
||||
}: {
|
||||
plaintext: string;
|
||||
key: string;
|
||||
}) => {
|
||||
const obj = AesGCM.encrypt(plaintext, key);
|
||||
const { ciphertext, iv, tag } = obj;
|
||||
|
||||
return {
|
||||
ciphertext,
|
||||
iv,
|
||||
tag
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return symmetrically decypted [ciphertext] using [iv], [tag],
|
||||
* and [key].
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.iv - iv
|
||||
* @param {String} obj.tag - tag
|
||||
* @param {String} obj.key - hex key
|
||||
*
|
||||
*/
|
||||
const decryptSymmetric = ({
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
key
|
||||
}: {
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
key: string;
|
||||
}): string => {
|
||||
const plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
export {
|
||||
generateKeyPair,
|
||||
encryptAsymmetric,
|
||||
decryptAsymmetric,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric
|
||||
};
|
166
backend/src/utils/crypto/index.ts
Normal file
166
backend/src/utils/crypto/index.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import crypto from 'crypto';
|
||||
import nacl from 'tweetnacl';
|
||||
import util from 'tweetnacl-util';
|
||||
import {
|
||||
IGenerateKeyPairOutput,
|
||||
IEncryptAsymmetricInput,
|
||||
IEncryptAsymmetricOutput,
|
||||
IDecryptAsymmetricInput,
|
||||
IEncryptSymmetricInput,
|
||||
IDecryptSymmetricInput
|
||||
} from '../../interfaces/utils';
|
||||
import { BadRequestError } from '../errors';
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
NONCE_BYTES_SIZE,
|
||||
BLOCK_SIZE_BYTES_16
|
||||
} from '../../variables';
|
||||
|
||||
/**
|
||||
* Return new base64, NaCl, public-private key pair.
|
||||
* @returns {Object} obj
|
||||
* @returns {String} obj.publicKey - (base64) NaCl, public key
|
||||
* @returns {String} obj.privateKey - (base64), NaCl, private key
|
||||
*/
|
||||
const generateKeyPair = (): IGenerateKeyPairOutput => {
|
||||
const pair = nacl.box.keyPair();
|
||||
|
||||
return ({
|
||||
publicKey: util.encodeBase64(pair.publicKey),
|
||||
privateKey: util.encodeBase64(pair.secretKey)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return assymmetrically encrypted [plaintext] using [publicKey] where
|
||||
* [publicKey] likely belongs to the recipient.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.plaintext - plaintext to encrypt
|
||||
* @param {String} obj.publicKey - (base64) Nacl public key of the recipient
|
||||
* @param {String} obj.privateKey - (base64) Nacl private key of the sender (current user)
|
||||
* @returns {Object} obj
|
||||
* @returns {String} obj.ciphertext - (base64) ciphertext
|
||||
* @returns {String} obj.nonce - (base64) nonce
|
||||
*/
|
||||
const encryptAsymmetric = ({
|
||||
plaintext,
|
||||
publicKey,
|
||||
privateKey
|
||||
}: IEncryptAsymmetricInput): IEncryptAsymmetricOutput => {
|
||||
const nonce = nacl.randomBytes(24);
|
||||
const ciphertext = nacl.box(
|
||||
util.decodeUTF8(plaintext),
|
||||
nonce,
|
||||
util.decodeBase64(publicKey),
|
||||
util.decodeBase64(privateKey)
|
||||
);
|
||||
|
||||
return {
|
||||
ciphertext: util.encodeBase64(ciphertext),
|
||||
nonce: util.encodeBase64(nonce)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
|
||||
* [privateKey] likely belongs to the recipient.
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.nonce - (base64) nonce
|
||||
* @param {String} obj.publicKey - (base64) public key of the sender
|
||||
* @param {String} obj.privateKey - (base64) private key of the receiver (current user)
|
||||
* @returns {String} plaintext - (utf8) plaintext
|
||||
*/
|
||||
const decryptAsymmetric = ({
|
||||
ciphertext,
|
||||
nonce,
|
||||
publicKey,
|
||||
privateKey
|
||||
}: IDecryptAsymmetricInput): string => {
|
||||
const plaintext: Uint8Array | null = nacl.box.open(
|
||||
util.decodeBase64(ciphertext),
|
||||
util.decodeBase64(nonce),
|
||||
util.decodeBase64(publicKey),
|
||||
util.decodeBase64(privateKey)
|
||||
);
|
||||
|
||||
if (plaintext == null) throw BadRequestError({
|
||||
message: 'Invalid ciphertext or keys'
|
||||
});
|
||||
|
||||
return util.encodeUTF8(plaintext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return symmetrically encrypted [plaintext] using [key].
|
||||
*
|
||||
* NOTE: THIS FUNCTION SHOULD NOT BE USED FOR ALL FUTURE
|
||||
* ENCRYPTION OPERATIONS UNLESS IT TOUCHES OLD FUNCTIONALITY
|
||||
* THAT USES IT. USE encryptSymmetric() instead
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.plaintext - (utf8) plaintext to encrypt
|
||||
* @param {String} obj.key - (hex) 128-bit key
|
||||
* @returns {Object} obj
|
||||
* @returns {String} obj.ciphertext (base64) ciphertext
|
||||
* @returns {String} obj.iv (base64) iv
|
||||
* @returns {String} obj.tag (base64) tag
|
||||
*/
|
||||
const encryptSymmetric128BitHexKeyUTF8 = ({
|
||||
plaintext,
|
||||
key
|
||||
}: IEncryptSymmetricInput) => {
|
||||
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, key, iv);
|
||||
|
||||
let ciphertext = cipher.update(plaintext, 'utf8', 'base64');
|
||||
ciphertext += cipher.final('base64');
|
||||
|
||||
return {
|
||||
ciphertext,
|
||||
iv: iv.toString('base64'),
|
||||
tag: cipher.getAuthTag().toString('base64')
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Return symmetrically decrypted [ciphertext] using [iv], [tag],
|
||||
* and [key].
|
||||
*
|
||||
* NOTE: THIS FUNCTION SHOULD NOT BE USED FOR ALL FUTURE
|
||||
* DECRYPTION OPERATIONS UNLESS IT TOUCHES OLD FUNCTIONALITY
|
||||
* THAT USES IT. USE decryptSymmetric() instead
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.ciphertext - ciphertext to decrypt
|
||||
* @param {String} obj.iv - (base64) 256-bit iv
|
||||
* @param {String} obj.tag - (base64) tag
|
||||
* @param {String} obj.key - (hex) 128-bit key
|
||||
* @returns {String} cleartext - the deciphered ciphertext
|
||||
*/
|
||||
const decryptSymmetric128BitHexKeyUTF8 = ({
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
key
|
||||
}: IDecryptSymmetricInput) => {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
ALGORITHM_AES_256_GCM,
|
||||
key,
|
||||
Buffer.from(iv, 'base64')
|
||||
);
|
||||
|
||||
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||
|
||||
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
|
||||
cleartext += decipher.final('utf8');
|
||||
|
||||
return cleartext;
|
||||
}
|
||||
|
||||
export {
|
||||
generateKeyPair,
|
||||
encryptAsymmetric,
|
||||
decryptAsymmetric,
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
decryptSymmetric128BitHexKeyUTF8
|
||||
};
|
324
backend/src/utils/setup/backfillData.ts
Normal file
324
backend/src/utils/setup/backfillData.ts
Normal file
@ -0,0 +1,324 @@
|
||||
import crypto from 'crypto';
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from '../crypto';
|
||||
import { EESecretService } from '../../ee/services';
|
||||
import { SecretVersion } from '../../ee/models';
|
||||
import {
|
||||
Secret,
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
Workspace,
|
||||
Bot,
|
||||
BackupPrivateKey,
|
||||
IntegrationAuth,
|
||||
} from '../../models';
|
||||
import {
|
||||
generateKeyPair
|
||||
} from '../../utils/crypto';
|
||||
import {
|
||||
client,
|
||||
getEncryptionKey,
|
||||
getRootEncryptionKey
|
||||
} from '../../config';
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../../variables';
|
||||
import { InternalServerError } from '../errors';
|
||||
|
||||
/**
|
||||
* Backfill secrets to ensure that they're all versioned and have
|
||||
* corresponding secret versions
|
||||
*/
|
||||
export const backfillSecretVersions = async () => {
|
||||
await Secret.updateMany(
|
||||
{ version: { $exists: false } },
|
||||
{ $set: { version: 1 } }
|
||||
);
|
||||
|
||||
const unversionedSecrets: ISecret[] = await Secret.aggregate([
|
||||
{
|
||||
$lookup: {
|
||||
from: "secretversions",
|
||||
localField: "_id",
|
||||
foreignField: "secret",
|
||||
as: "versions",
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
versions: { $size: 0 },
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
if (unversionedSecrets.length > 0) {
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: unversionedSecrets.map(
|
||||
(s, idx) =>
|
||||
new SecretVersion({
|
||||
...s,
|
||||
secret: s._id,
|
||||
version: s.version ? s.version : 1,
|
||||
isDeleted: false,
|
||||
workspace: s.workspace,
|
||||
environment: s.environment,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
})
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backfill workspace bots to ensure that every workspace has a bot
|
||||
*/
|
||||
export const backfillBots = async () => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const workspaceIdsWithBot = await Bot.distinct('workspace');
|
||||
const workspaceIdsToAddBot = await Workspace.distinct('_id', {
|
||||
_id: {
|
||||
$nin: workspaceIdsWithBot
|
||||
}
|
||||
});
|
||||
|
||||
if (workspaceIdsToAddBot.length === 0) return;
|
||||
|
||||
const botsToInsert = await Promise.all(
|
||||
workspaceIdsToAddBot.map(async (workspaceToAddBot) => {
|
||||
const { publicKey, privateKey } = generateKeyPair();
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||
|
||||
return new Bot({
|
||||
name: 'Infisical Bot',
|
||||
workspace: workspaceToAddBot,
|
||||
isActive: false,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
});
|
||||
} else if (encryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: privateKey,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return new Bot({
|
||||
name: 'Infisical Bot',
|
||||
workspace: workspaceToAddBot,
|
||||
isActive: false,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to backfill workspace bots due to missing encryption key'
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
await Bot.insertMany(botsToInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backfill secret blind index data to ensure that every workspace
|
||||
* has a secret blind index data
|
||||
*/
|
||||
export const backfillSecretBlindIndexData = async () => {
|
||||
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace');
|
||||
const workspaceIdsToBlindIndex = await Workspace.distinct('_id', {
|
||||
_id: {
|
||||
$nin: workspaceIdsBlindIndexed
|
||||
}
|
||||
});
|
||||
|
||||
if (workspaceIdsToBlindIndex.length === 0) return;
|
||||
|
||||
const secretBlindIndexDataToInsert = await Promise.all(
|
||||
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = client.encryptSymmetric(salt, rootEncryptionKey)
|
||||
|
||||
return new SecretBlindIndexData({
|
||||
workspace: workspaceToBlindIndex,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
});
|
||||
} else if (encryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: salt,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return new SecretBlindIndexData({
|
||||
workspace: workspaceToBlindIndex,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to backfill secret blind index data due to missing encryption key'
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backfill Secret, SecretVersion, SecretBlindIndexData, Bot,
|
||||
* BackupPrivateKey, IntegrationAuth collections to ensure that
|
||||
* they all have encryption metadata documented
|
||||
*/
|
||||
export const backfillEncryptionMetadata = async () => {
|
||||
|
||||
// backfill secret encryption metadata
|
||||
await Secret.updateMany(
|
||||
{
|
||||
algorithm: {
|
||||
$exists: false
|
||||
},
|
||||
keyEncoding: {
|
||||
$exists: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// backfill secret version encryption metadata
|
||||
await SecretVersion.updateMany(
|
||||
{
|
||||
algorithm: {
|
||||
$exists: false
|
||||
},
|
||||
keyEncoding: {
|
||||
$exists: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// backfill secret blind index encryption metadata
|
||||
await SecretBlindIndexData.updateMany(
|
||||
{
|
||||
algorithm: {
|
||||
$exists: false
|
||||
},
|
||||
keyEncoding: {
|
||||
$exists: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// backfill bot encryption metadata
|
||||
await Bot.updateMany(
|
||||
{
|
||||
algorithm: {
|
||||
$exists: false
|
||||
},
|
||||
keyEncoding: {
|
||||
$exists: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// backfill backup private key encryption metadata
|
||||
await BackupPrivateKey.updateMany(
|
||||
{
|
||||
algorithm: {
|
||||
$exists: false
|
||||
},
|
||||
keyEncoding: {
|
||||
$exists: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// backfill integration auth encryption metadata
|
||||
await IntegrationAuth.updateMany(
|
||||
{
|
||||
algorithm: {
|
||||
$exists: false
|
||||
},
|
||||
keyEncoding: {
|
||||
$exists: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
78
backend/src/utils/setup/index.ts
Normal file
78
backend/src/utils/setup/index.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { DatabaseService, TelemetryService } from '../../services';
|
||||
import { setTransporter } from '../../helpers/nodemailer';
|
||||
import { EELicenseService } from '../../ee/services';
|
||||
import { initSmtp } from '../../services/smtp';
|
||||
import { createTestUserForDevelopment } from '../addDevelopmentUser'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { patchRouterParam } = require('../patchAsyncRoutes');
|
||||
import {
|
||||
validateEncryptionKeysConfig
|
||||
} from './validateConfig';
|
||||
import {
|
||||
backfillSecretVersions,
|
||||
backfillBots,
|
||||
backfillSecretBlindIndexData,
|
||||
backfillEncryptionMetadata
|
||||
} from './backfillData';
|
||||
import {
|
||||
reencryptBotPrivateKeys,
|
||||
reencryptSecretBlindIndexDataSalts
|
||||
} from './reencryptData';
|
||||
import {
|
||||
getNodeEnv,
|
||||
getMongoURL,
|
||||
getSentryDSN
|
||||
} from '../../config';
|
||||
|
||||
/**
|
||||
* Prepare Infisical upon startup. This includes tasks like:
|
||||
* - Log initial telemetry message
|
||||
* - Initializing SMTP configuration
|
||||
* - Initializing the instance global feature set (if applicable)
|
||||
* - Initializing the database connection
|
||||
* - Initializing Sentry
|
||||
* - Backfilling data
|
||||
* - Re-encrypting data
|
||||
*/
|
||||
export const setup = async () => {
|
||||
patchRouterParam();
|
||||
await validateEncryptionKeysConfig();
|
||||
await TelemetryService.logTelemetryMessage();
|
||||
|
||||
// initializing SMTP configuration
|
||||
setTransporter(await initSmtp());
|
||||
|
||||
// initializing global feature set
|
||||
await EELicenseService.initGlobalFeatureSet();
|
||||
|
||||
// initializing the database connection
|
||||
await DatabaseService.initDatabase(await getMongoURL());
|
||||
|
||||
/**
|
||||
* NOTE: the order in this setup function is critical.
|
||||
* It is important to backfill data before performing any re-encryption functionality.
|
||||
*/
|
||||
|
||||
// backfilling data to catch up with new collections and updated fields
|
||||
await backfillSecretVersions();
|
||||
await backfillBots();
|
||||
await backfillSecretBlindIndexData();
|
||||
await backfillEncryptionMetadata();
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
// to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
await reencryptBotPrivateKeys();
|
||||
await reencryptSecretBlindIndexDataSalts();
|
||||
|
||||
// initializing Sentry
|
||||
Sentry.init({
|
||||
dsn: await getSentryDSN(),
|
||||
tracesSampleRate: 1.0,
|
||||
debug: (await getNodeEnv()) === 'production' ? false : true,
|
||||
environment: (await getNodeEnv())
|
||||
});
|
||||
|
||||
await createTestUserForDevelopment();
|
||||
}
|
||||
|
124
backend/src/utils/setup/reencryptData.ts
Normal file
124
backend/src/utils/setup/reencryptData.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
Bot,
|
||||
IBot,
|
||||
ISecretBlindIndexData,
|
||||
SecretBlindIndexData
|
||||
} from '../../models';
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from '../../utils/crypto';
|
||||
import {
|
||||
client,
|
||||
getEncryptionKey,
|
||||
getRootEncryptionKey
|
||||
} from '../../config';
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64
|
||||
} from '../../variables';
|
||||
|
||||
/**
|
||||
* Re-encrypt bot private keys from hex 128-bit ENCRYPTION_KEY
|
||||
* to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
*/
|
||||
export const reencryptBotPrivateKeys = async () => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
if (encryptionKey && rootEncryptionKey) {
|
||||
// 1: re-encrypt bot private keys under ROOT_ENCRYPTION_KEY
|
||||
const bots = await Bot.find({
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).select('+encryptedPrivateKey iv tag algorithm keyEncoding');
|
||||
|
||||
if (bots.length === 0) return;
|
||||
|
||||
const operationsBot = await Promise.all(
|
||||
bots.map(async (bot: IBot) => {
|
||||
|
||||
const privateKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||
|
||||
return ({
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: bot._id
|
||||
},
|
||||
update: {
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
await Bot.bulkWrite(operationsBot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encrypt secret blind index data salts from hex 128-bit ENCRYPTION_KEY
|
||||
* to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
*/
|
||||
export const reencryptSecretBlindIndexDataSalts = async () => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
if (encryptionKey && rootEncryptionKey) {
|
||||
const secretBlindIndexData = await SecretBlindIndexData.find({
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).select('+encryptedSaltCiphertext +saltIV +saltTag +algorithm +keyEncoding');
|
||||
|
||||
if (secretBlindIndexData.length == 0) return;
|
||||
|
||||
const operationsSecretBlindIndexData = await Promise.all(
|
||||
secretBlindIndexData.map(async (secretBlindIndexDatum: ISecretBlindIndexData) => {
|
||||
|
||||
const salt = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secretBlindIndexDatum.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexDatum.saltIV,
|
||||
tag: secretBlindIndexDatum.saltTag,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||
|
||||
return ({
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: secretBlindIndexDatum._id
|
||||
},
|
||||
update: {
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
await SecretBlindIndexData.bulkWrite(operationsSecretBlindIndexData);
|
||||
}
|
||||
}
|
61
backend/src/utils/setup/validateConfig.ts
Normal file
61
backend/src/utils/setup/validateConfig.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import {
|
||||
getEncryptionKey,
|
||||
getRootEncryptionKey
|
||||
} from '../../config';
|
||||
import {
|
||||
InternalServerError
|
||||
} from '../../utils/errors';
|
||||
|
||||
/**
|
||||
* Validate ENCRYPTION_KEY and ROOT_ENCRYPTION_KEY. Specifically:
|
||||
* - ENCRYPTION_KEY is a hex, 128-bit string
|
||||
* - ROOT_ENCRYPTION_KEY is a base64, 128-bit string
|
||||
* - Either ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY are present
|
||||
*
|
||||
* - Encrypted data is consistent with the passed in encryption keys
|
||||
*
|
||||
* NOTE 1: ENCRYPTION_KEY is being transitioned to ROOT_ENCRYPTION_KEY
|
||||
* NOTE 2: In the future, we will have a superior validation function
|
||||
* built into the SDK.
|
||||
*/
|
||||
export const validateEncryptionKeysConfig = async () => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
if (
|
||||
(encryptionKey === undefined || encryptionKey === "") &&
|
||||
(rootEncryptionKey === undefined || rootEncryptionKey === "")
|
||||
) throw InternalServerError({
|
||||
message: "Failed to find required root encryption key environment variable. Please make sure that you're passing in a ROOT_ENCRYPTION_KEY environment variable."
|
||||
});
|
||||
|
||||
if (encryptionKey && encryptionKey !== '') {
|
||||
// validate [encryptionKey]
|
||||
|
||||
const keyBuffer = Buffer.from(encryptionKey, 'hex');
|
||||
const decoded = keyBuffer.toString('hex');
|
||||
|
||||
if (decoded !== encryptionKey) throw InternalServerError({
|
||||
message: 'Failed to validate that the encryption key is correctly encoded in hex.'
|
||||
});
|
||||
|
||||
if (keyBuffer.length !== 16) throw InternalServerError({
|
||||
message: 'Failed to validate that the encryption key is a 128-bit hex string.'
|
||||
});
|
||||
}
|
||||
|
||||
if (rootEncryptionKey && rootEncryptionKey !== '') {
|
||||
// validate [rootEncryptionKey]
|
||||
|
||||
const keyBuffer = Buffer.from(rootEncryptionKey, 'base64')
|
||||
const decoded = keyBuffer.toString('base64');
|
||||
|
||||
if (decoded !== rootEncryptionKey) throw InternalServerError({
|
||||
message: 'Failed to validate that the root encryption key is correctly encoded in base64'
|
||||
});
|
||||
|
||||
if (keyBuffer.length !== 32) throw InternalServerError({
|
||||
message: 'Failed to validate that the encryption key is a 256-bit base64 string'
|
||||
});
|
||||
}
|
||||
}
|
98
backend/src/validation/bot.ts
Normal file
98
backend/src/validation/bot.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
Bot,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
import { validateUserClientForWorkspace } from './user';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
BotNotFoundError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for bot with id [botId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.botId - id of bot to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
*/
|
||||
export const validateClientForBot = async ({
|
||||
authData,
|
||||
botId,
|
||||
acceptedRoles,
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
botId: Types.ObjectId;
|
||||
acceptedRoles: Array<"admin" | "member">;
|
||||
}) => {
|
||||
const bot = await Bot.findById(botId);
|
||||
|
||||
if (!bot) throw BotNotFoundError();
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_JWT &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: bot.workspace,
|
||||
acceptedRoles,
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||
authData.authPayload instanceof ServiceAccount
|
||||
) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: bot.workspace,
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
||||
authData.authPayload instanceof ServiceTokenData
|
||||
) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for bot",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_API_KEY &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: bot.workspace,
|
||||
acceptedRoles,
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
throw BotNotFoundError({
|
||||
message: "Failed client authorization for bot",
|
||||
});
|
||||
};
|
10
backend/src/validation/index.ts
Normal file
10
backend/src/validation/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export * from './workspace';
|
||||
export * from './bot';
|
||||
export * from './integration';
|
||||
export * from './integrationAuth';
|
||||
export * from './membership';
|
||||
export * from './membershipOrg';
|
||||
export * from './organization';
|
||||
export * from './secrets';
|
||||
export * from './serviceAccount';
|
||||
export * from './serviceTokenData';
|
103
backend/src/validation/integration.ts
Normal file
103
backend/src/validation/integration.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
import { validateUserClientForWorkspace } from './user';
|
||||
import { IntegrationService } from '../services';
|
||||
import {
|
||||
IntegrationNotFoundError,
|
||||
IntegrationAuthNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for integration with id [integrationId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.integrationId - id of integration to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateClientForIntegration = async ({
|
||||
authData,
|
||||
integrationId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
integrationId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
|
||||
const integration = await Integration.findById(integrationId);
|
||||
if (!integration) throw IntegrationNotFoundError();
|
||||
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integration.integrationAuth)
|
||||
.select(
|
||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||
|
||||
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id
|
||||
})).accessToken;
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integration.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: integration.workspace
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service token authorization for integration'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: integration.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return ({ integration, accessToken });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for integration'
|
||||
});
|
||||
}
|
@ -20,8 +20,8 @@ import {
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import { IntegrationService } from '../services';
|
||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||
import { validateUserClientForWorkspace } from './user';
|
||||
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
|
94
backend/src/validation/membership.ts
Normal file
94
backend/src/validation/membership.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
Membership,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
import { validateUserClientForWorkspace } from './user';
|
||||
import { validateServiceTokenDataClientForWorkspace } from './serviceTokenData';
|
||||
import {
|
||||
MembershipNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for membership with id [membershipId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.membershipId - id of membership to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspaceRoles
|
||||
* @returns {Membership} - validated membership
|
||||
*/
|
||||
export const validateClientForMembership = async ({
|
||||
authData,
|
||||
membershipId,
|
||||
acceptedRoles
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
membershipId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
}) => {
|
||||
|
||||
const membership = await Membership.findById(membershipId);
|
||||
|
||||
if (!membership) throw MembershipNotFoundError({
|
||||
message: 'Failed to find membership'
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: membership.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: membership.workspace
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId: new Types.ObjectId(membership.workspace)
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId: membership.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for membership'
|
||||
});
|
||||
}
|
93
backend/src/validation/membershipOrg.ts
Normal file
93
backend/src/validation/membershipOrg.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
MembershipOrg,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import {
|
||||
validateMembershipOrg
|
||||
} from '../helpers/membershipOrg';
|
||||
import {
|
||||
MembershipOrgNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.membershipOrgId - id of organization membership to validate against
|
||||
* @param {Array<'owner' | 'admin' | 'member'>} obj.acceptedRoles - accepted organization roles
|
||||
* @param {MembershipOrg} - validated organization membership
|
||||
*/
|
||||
export const validateClientForMembershipOrg = async ({
|
||||
authData,
|
||||
membershipOrgId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
membershipOrgId: Types.ObjectId;
|
||||
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||
}) => {
|
||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||
|
||||
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||
message: 'Failed to find organization membership '
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({
|
||||
message: 'Failed service account client authorization for organization membership'
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed service account client authorization for organization membership'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for organization membership'
|
||||
});
|
||||
}
|
104
backend/src/validation/organization.ts
Normal file
104
backend/src/validation/organization.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
Organization,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import {
|
||||
OrganizationNotFoundError,
|
||||
UnauthorizedRequestError
|
||||
} from '../utils/errors';
|
||||
import { validateUserClientForOrganization } from './user';
|
||||
import { validateServiceAccountClientForOrganization } from './serviceAccount';
|
||||
|
||||
/**
|
||||
* Validate accepted clients for organization with id [organizationId]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.organizationId - id of organization to validate against
|
||||
*/
|
||||
export const validateClientForOrganization = async ({
|
||||
authData,
|
||||
organizationId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
organizationId: Types.ObjectId;
|
||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||
acceptedStatuses: Array<"invited" | "accepted">;
|
||||
}) => {
|
||||
const organization = await Organization.findById(organizationId);
|
||||
|
||||
if (!organization) {
|
||||
throw OrganizationNotFoundError({
|
||||
message: "Failed to find organization",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_JWT &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
const membershipOrg = await validateUserClientForOrganization({
|
||||
user: authData.authPayload,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
});
|
||||
|
||||
return { organization, membershipOrg };
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||
authData.authPayload instanceof ServiceAccount
|
||||
) {
|
||||
await validateServiceAccountClientForOrganization({
|
||||
serviceAccount: authData.authPayload,
|
||||
organization,
|
||||
});
|
||||
|
||||
return { organization };
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
|
||||
authData.authPayload instanceof ServiceTokenData
|
||||
) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for organization",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
authData.authMode === AUTH_MODE_API_KEY &&
|
||||
authData.authPayload instanceof User
|
||||
) {
|
||||
const membershipOrg = await validateUserClientForOrganization({
|
||||
user: authData.authPayload,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
});
|
||||
|
||||
return { organization, membershipOrg };
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed client authorization for organization",
|
||||
});
|
||||
};
|
174
backend/src/validation/secrets.ts
Normal file
174
backend/src/validation/secrets.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
ISecret,
|
||||
Secret,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import { validateServiceAccountClientForWorkspace, validateServiceAccountClientForSecrets } from './serviceAccount';
|
||||
import { validateUserClientForSecret, validateUserClientForSecrets } from './user';
|
||||
import { validateServiceTokenDataClientForWorkspace, validateServiceTokenDataClientForSecrets } from './serviceTokenData';
|
||||
import { AuthData } from '../interfaces/middleware';
|
||||
import {
|
||||
SecretNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
BadRequestError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for secrets with id [secretId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateClientForSecret = async ({
|
||||
authData,
|
||||
secretId,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: AuthData;
|
||||
secretId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
const secret = await Secret.findById(secretId);
|
||||
|
||||
if (!secret) throw SecretNotFoundError({
|
||||
message: 'Failed to find secret'
|
||||
});
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecret({
|
||||
user: authData.authPayload,
|
||||
secret,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId: secret.workspace,
|
||||
environment: secret.environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId: secret.workspace,
|
||||
environment: secret.environment
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecret({
|
||||
user: authData.authPayload,
|
||||
secret,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for secret'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for secrets with ids [secretIds] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId[]} obj.secretIds - id of workspace to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateClientForSecrets = async ({
|
||||
authData,
|
||||
secretIds,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: AuthData;
|
||||
secretIds: Types.ObjectId[];
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
secrets = await Secret.find({
|
||||
_id: {
|
||||
$in: secretIds
|
||||
}
|
||||
});
|
||||
|
||||
if (secrets.length != secretIds.length) {
|
||||
throw BadRequestError({ message: 'Failed to validate non-existent secrets' })
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecrets({
|
||||
user: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForSecrets({
|
||||
serviceAccount: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForSecrets({
|
||||
serviceTokenData: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
await validateUserClientForSecrets({
|
||||
user: authData.authPayload,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for secrets resource'
|
||||
});
|
||||
}
|
@ -9,9 +9,9 @@ import {
|
||||
IServiceTokenData,
|
||||
ISecret,
|
||||
IOrganization,
|
||||
IServiceAccountWorkspacePermission,
|
||||
ServiceAccountWorkspacePermission
|
||||
} from '../models';
|
||||
import { validateUserClientForServiceAccount } from './user';
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
@ -25,11 +25,8 @@ import {
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import {
|
||||
validateUserClientForServiceAccount
|
||||
} from '../helpers/user';
|
||||
|
||||
const validateClientForServiceAccount = async ({
|
||||
export const validateClientForServiceAccount = async ({
|
||||
authData,
|
||||
serviceAccountId,
|
||||
requiredPermissions
|
||||
@ -100,7 +97,7 @@ const validateClientForServiceAccount = async ({
|
||||
* @param {String} environment - (optional) environment in workspace to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceAccountClientForWorkspace = async ({
|
||||
export const validateServiceAccountClientForWorkspace = async ({
|
||||
serviceAccount,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -169,7 +166,7 @@ const validateClientForServiceAccount = async ({
|
||||
* @param {Secret[]} secrets - secrets to validate against
|
||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceAccountClientForSecrets = async ({
|
||||
export const validateServiceAccountClientForSecrets = async ({
|
||||
serviceAccount,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
@ -226,7 +223,7 @@ const validateClientForServiceAccount = async ({
|
||||
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
|
||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceAccountClientForServiceAccount = ({
|
||||
export const validateServiceAccountClientForServiceAccount = ({
|
||||
serviceAccount,
|
||||
targetServiceAccount,
|
||||
requiredPermissions
|
||||
@ -248,7 +245,7 @@ const validateServiceAccountClientForServiceAccount = ({
|
||||
* @param {User} obj.user - service account client
|
||||
* @param {Organization} obj.organization - organization to validate against
|
||||
*/
|
||||
const validateServiceAccountClientForOrganization = async ({
|
||||
export const validateServiceAccountClientForOrganization = async ({
|
||||
serviceAccount,
|
||||
organization
|
||||
}: {
|
||||
@ -260,12 +257,4 @@ const validateServiceAccountClientForOrganization = async ({
|
||||
message: 'Failed service account authorization for the given organization'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForServiceAccount,
|
||||
validateServiceAccountClientForWorkspace,
|
||||
validateServiceAccountClientForSecrets,
|
||||
validateServiceAccountClientForServiceAccount,
|
||||
validateServiceAccountClientForOrganization
|
||||
}
|
@ -18,8 +18,8 @@ import {
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||
import { validateUserClientForWorkspace } from './user';
|
||||
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for service token with id [serviceTokenId] based
|
||||
@ -29,7 +29,7 @@ import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAcco
|
||||
* @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
*/
|
||||
const validateClientForServiceTokenData = async ({
|
||||
export const validateClientForServiceTokenData = async ({
|
||||
authData,
|
||||
serviceTokenDataId,
|
||||
acceptedRoles
|
||||
@ -100,7 +100,7 @@ const validateClientForServiceTokenData = async ({
|
||||
* @param {String} environment - (optional) environment in workspace to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceTokenDataClientForWorkspace = async ({
|
||||
export const validateServiceTokenDataClientForWorkspace = async ({
|
||||
serviceTokenData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -146,7 +146,7 @@ const validateClientForServiceTokenData = async ({
|
||||
* @param {Secret[]} secrets - secrets to validate against
|
||||
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
const validateServiceTokenDataClientForSecrets = async ({
|
||||
export const validateServiceTokenDataClientForSecrets = async ({
|
||||
serviceTokenData,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
@ -179,10 +179,4 @@ const validateClientForServiceTokenData = async ({
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForServiceTokenData,
|
||||
validateServiceTokenDataClientForWorkspace,
|
||||
validateServiceTokenDataClientForSecrets
|
||||
}
|
209
backend/src/validation/user.ts
Normal file
209
backend/src/validation/user.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
ISecret,
|
||||
IServiceAccount,
|
||||
Membership,
|
||||
IOrganization,
|
||||
} from '../models';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import _ from 'lodash';
|
||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||
import {
|
||||
validateMembershipOrg
|
||||
} from '../helpers/membershipOrg';
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access workspace
|
||||
* with id [workspaceId] and its environment [environment] with required permissions
|
||||
* [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||
* @param {String} environment - (optional) environment in workspace to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateUserClientForWorkspace = async ({
|
||||
user,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
// validate user membership in workspace
|
||||
const membership = await validateMembership({
|
||||
userId: user._id,
|
||||
workspaceId,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
let runningIsDisallowed = false;
|
||||
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||
switch (requiredPermission) {
|
||||
case PERMISSION_READ_SECRETS:
|
||||
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||
break;
|
||||
case PERMISSION_WRITE_SECRETS:
|
||||
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (runningIsDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return membership;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access secret [secret]
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateUserClientForSecret = async ({
|
||||
user,
|
||||
secret,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
secret: ISecret;
|
||||
acceptedRoles?: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
const membership = await validateMembership({
|
||||
userId: user._id,
|
||||
workspaceId: secret.workspace,
|
||||
acceptedRoles
|
||||
});
|
||||
|
||||
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||
|
||||
if (isDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'You do not have the required permissions to perform this action'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access secrets [secrets]
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateUserClientForSecrets = async ({
|
||||
user,
|
||||
secrets,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
secrets: ISecret[];
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
// TODO: add acceptedRoles?
|
||||
|
||||
const userMemberships = await Membership.find({ user: user._id })
|
||||
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
||||
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
|
||||
|
||||
// for each secret check if the secret belongs to a workspace the user is a member of
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
if (!workspaceIdsSet.has(secret.workspace.toString())) {
|
||||
throw BadRequestError({
|
||||
message: 'Failed authorization for the secret'
|
||||
});
|
||||
}
|
||||
|
||||
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||
|
||||
if (isDisallowed) {
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'You do not have the required permissions to perform this action'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access service account [serviceAccount]
|
||||
* with required permissions [requiredPermissions]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
|
||||
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateUserClientForServiceAccount = async ({
|
||||
user,
|
||||
serviceAccount,
|
||||
requiredPermissions
|
||||
}: {
|
||||
user: IUser;
|
||||
serviceAccount: IServiceAccount;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
if (!serviceAccount.user.equals(user._id)) {
|
||||
// case: user who created service account is not the
|
||||
// same user that is on the request
|
||||
await validateMembershipOrg({
|
||||
userId: user._id,
|
||||
organizationId: serviceAccount.organization,
|
||||
acceptedRoles: [],
|
||||
acceptedStatuses: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access organization [organization]
|
||||
* @param {Object} obj
|
||||
* @param {User} obj.user - user client
|
||||
* @param {Organization} obj.organization - organization to validate against
|
||||
*/
|
||||
export const validateUserClientForOrganization = async ({
|
||||
user,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
user: IUser;
|
||||
organization: IOrganization;
|
||||
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||
}) => {
|
||||
const membershipOrg = await validateMembershipOrg({
|
||||
userId: user._id,
|
||||
organizationId: organization._id,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
}
|
124
backend/src/validation/workspace.ts
Normal file
124
backend/src/validation/workspace.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
Workspace,
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData,
|
||||
SecretBlindIndexData
|
||||
} from '../models';
|
||||
import { validateServiceAccountClientForWorkspace } from './serviceAccount';
|
||||
import { validateUserClientForWorkspace } from './user';
|
||||
import { validateServiceTokenDataClientForWorkspace } from './serviceTokenData';
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
WorkspaceNotFoundError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||
* on any known permissions.
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.authData - authenticated client details
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateClientForWorkspace = async ({
|
||||
authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled: boolean;
|
||||
}) => {
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
|
||||
if (!workspace) throw WorkspaceNotFoundError({
|
||||
message: 'Failed to find workspace'
|
||||
});
|
||||
|
||||
if (requireBlindIndicesEnabled) {
|
||||
// case: blind indices are not enabled for secrets in this workspace
|
||||
// (i.e. workspace was created before blind indices were introduced
|
||||
// and no admin has enabled it)
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!secretBlindIndexData) throw UnauthorizedRequestError({
|
||||
message: 'Failed workspace authorization due to blind indices not being enabled'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
const membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return ({ membership, workspace });
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||
await validateServiceAccountClientForWorkspace({
|
||||
serviceAccount: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||
const membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return ({ membership, workspace });
|
||||
}
|
||||
|
||||
throw UnauthorizedRequestError({
|
||||
message: 'Failed client authorization for workspace'
|
||||
});
|
||||
}
|
@ -1,15 +1,6 @@
|
||||
const ACTION_LOGIN = 'login';
|
||||
const ACTION_LOGOUT = 'logout';
|
||||
const ACTION_ADD_SECRETS = 'addSecrets';
|
||||
const ACTION_DELETE_SECRETS = 'deleteSecrets';
|
||||
const ACTION_UPDATE_SECRETS = 'updateSecrets';
|
||||
const ACTION_READ_SECRETS = 'readSecrets';
|
||||
|
||||
export {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_READ_SECRETS
|
||||
}
|
||||
export const ACTION_LOGIN = 'login';
|
||||
export const ACTION_LOGOUT = 'logout';
|
||||
export const ACTION_ADD_SECRETS = 'addSecrets';
|
||||
export const ACTION_DELETE_SECRETS = 'deleteSecrets';
|
||||
export const ACTION_UPDATE_SECRETS = 'updateSecrets';
|
||||
export const ACTION_READ_SECRETS = 'readSecrets';
|
@ -1,11 +1,4 @@
|
||||
const AUTH_MODE_JWT = 'jwt';
|
||||
const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount';
|
||||
const AUTH_MODE_SERVICE_TOKEN = 'serviceToken';
|
||||
const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate
|
||||
|
||||
export {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
}
|
||||
export const AUTH_MODE_JWT = 'jwt';
|
||||
export const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount';
|
||||
export const AUTH_MODE_SERVICE_TOKEN = 'serviceToken';
|
||||
export const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate
|
7
backend/src/variables/crypto.ts
Normal file
7
backend/src/variables/crypto.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const ALGORITHM_AES_256_GCM = 'aes-256-gcm';
|
||||
export const NONCE_BYTES_SIZE = 12;
|
||||
export const BLOCK_SIZE_BYTES_16 = 16;
|
||||
|
||||
export const ENCODING_SCHEME_UTF8 = 'utf8';
|
||||
export const ENCODING_SCHEME_HEX = 'hex';
|
||||
export const ENCODING_SCHEME_BASE64 = 'base64';
|
@ -1,14 +1,6 @@
|
||||
// environments
|
||||
const ENV_DEV = 'dev';
|
||||
const ENV_TESTING = 'test';
|
||||
const ENV_STAGING = 'staging';
|
||||
const ENV_PROD = 'prod';
|
||||
const ENV_SET = new Set([ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]);
|
||||
|
||||
export {
|
||||
ENV_DEV,
|
||||
ENV_TESTING,
|
||||
ENV_STAGING,
|
||||
ENV_PROD,
|
||||
ENV_SET
|
||||
}
|
||||
export const ENV_DEV = 'dev';
|
||||
export const ENV_TESTING = 'test';
|
||||
export const ENV_STAGING = 'staging';
|
||||
export const ENV_PROD = 'prod';
|
||||
export const ENV_SET = new Set([ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]);
|
@ -1,7 +1,2 @@
|
||||
const EVENT_PUSH_SECRETS = 'pushSecrets';
|
||||
const EVENT_PULL_SECRETS = 'pullSecrets';
|
||||
|
||||
export {
|
||||
EVENT_PUSH_SECRETS,
|
||||
EVENT_PULL_SECRETS
|
||||
}
|
||||
export const EVENT_PUSH_SECRETS = 'pushSecrets';
|
||||
export const EVENT_PULL_SECRETS = 'pullSecrets';
|
@ -1,154 +1,13 @@
|
||||
import {
|
||||
ENV_DEV,
|
||||
ENV_TESTING,
|
||||
ENV_STAGING,
|
||||
ENV_PROD,
|
||||
ENV_SET,
|
||||
} from "./environment";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_OAUTH2,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_API_URL,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
INTEGRATION_NETLIFY_API_URL,
|
||||
INTEGRATION_RENDER_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
INTEGRATION_TRAVISCI_API_URL,
|
||||
INTEGRATION_SUPABASE_API_URL,
|
||||
getIntegrationOptions
|
||||
} from "./integration";
|
||||
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from "./organization";
|
||||
import { SECRET_SHARED, SECRET_PERSONAL } from "./secret";
|
||||
import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from "./event";
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_READ_SECRETS
|
||||
} from './action';
|
||||
import {
|
||||
SMTP_HOST_SENDGRID,
|
||||
SMTP_HOST_MAILGUN,
|
||||
SMTP_HOST_SOCKETLABS,
|
||||
SMTP_HOST_ZOHOMAIL,
|
||||
SMTP_HOST_GMAIL
|
||||
} from './smtp';
|
||||
import { PLAN_STARTER, PLAN_PRO } from './stripe';
|
||||
import {
|
||||
MFA_METHOD_EMAIL
|
||||
} from './user';
|
||||
import {
|
||||
TOKEN_EMAIL_CONFIRMATION,
|
||||
TOKEN_EMAIL_MFA,
|
||||
TOKEN_EMAIL_ORG_INVITATION,
|
||||
TOKEN_EMAIL_PASSWORD_RESET
|
||||
} from './token';
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from './permission';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from './authentication';
|
||||
|
||||
export {
|
||||
OWNER,
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
INVITED,
|
||||
ACCEPTED,
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
ENV_DEV,
|
||||
ENV_TESTING,
|
||||
ENV_STAGING,
|
||||
ENV_PROD,
|
||||
ENV_SET,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_OAUTH2,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_API_URL,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
INTEGRATION_NETLIFY_API_URL,
|
||||
INTEGRATION_RENDER_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
INTEGRATION_TRAVISCI_API_URL,
|
||||
INTEGRATION_SUPABASE_API_URL,
|
||||
EVENT_PUSH_SECRETS,
|
||||
EVENT_PULL_SECRETS,
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
getIntegrationOptions,
|
||||
SMTP_HOST_SENDGRID,
|
||||
SMTP_HOST_MAILGUN,
|
||||
SMTP_HOST_SOCKETLABS,
|
||||
SMTP_HOST_ZOHOMAIL,
|
||||
SMTP_HOST_GMAIL,
|
||||
PLAN_STARTER,
|
||||
PLAN_PRO,
|
||||
MFA_METHOD_EMAIL,
|
||||
TOKEN_EMAIL_CONFIRMATION,
|
||||
TOKEN_EMAIL_MFA,
|
||||
TOKEN_EMAIL_ORG_INVITATION,
|
||||
TOKEN_EMAIL_PASSWORD_RESET,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
};
|
||||
export * from './action';
|
||||
export * from './authentication';
|
||||
export * from './crypto';
|
||||
export * from './environment';
|
||||
export * from './event';
|
||||
export * from './integration';
|
||||
export * from './organization';
|
||||
export * from './permission';
|
||||
export * from './secret';
|
||||
export * from './smtp';
|
||||
export * from './stripe';
|
||||
export * from './token';
|
||||
export * from './user';
|
||||
|
@ -8,21 +8,21 @@ import {
|
||||
} from '../config';
|
||||
|
||||
// integrations
|
||||
const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
|
||||
const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store';
|
||||
const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager';
|
||||
const INTEGRATION_HEROKU = "heroku";
|
||||
const INTEGRATION_VERCEL = "vercel";
|
||||
const INTEGRATION_NETLIFY = "netlify";
|
||||
const INTEGRATION_GITHUB = "github";
|
||||
const INTEGRATION_GITLAB = "gitlab";
|
||||
const INTEGRATION_RENDER = "render";
|
||||
const INTEGRATION_RAILWAY = "railway";
|
||||
const INTEGRATION_FLYIO = "flyio";
|
||||
const INTEGRATION_CIRCLECI = "circleci";
|
||||
const INTEGRATION_TRAVISCI = "travisci";
|
||||
const INTEGRATION_SUPABASE = 'supabase';
|
||||
const INTEGRATION_SET = new Set([
|
||||
export const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
|
||||
export const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store';
|
||||
export const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager';
|
||||
export const INTEGRATION_HEROKU = "heroku";
|
||||
export const INTEGRATION_VERCEL = "vercel";
|
||||
export const INTEGRATION_NETLIFY = "netlify";
|
||||
export const INTEGRATION_GITHUB = "github";
|
||||
export const INTEGRATION_GITLAB = "gitlab";
|
||||
export const INTEGRATION_RENDER = "render";
|
||||
export const INTEGRATION_RAILWAY = "railway";
|
||||
export const INTEGRATION_FLYIO = "flyio";
|
||||
export const INTEGRATION_CIRCLECI = "circleci";
|
||||
export const INTEGRATION_TRAVISCI = "travisci";
|
||||
export const INTEGRATION_SUPABASE = 'supabase';
|
||||
export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
@ -37,31 +37,31 @@ const INTEGRATION_SET = new Set([
|
||||
]);
|
||||
|
||||
// integration types
|
||||
const INTEGRATION_OAUTH2 = "oauth2";
|
||||
export const INTEGRATION_OAUTH2 = "oauth2";
|
||||
|
||||
// integration oauth endpoints
|
||||
const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`;
|
||||
const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
||||
const INTEGRATION_VERCEL_TOKEN_URL =
|
||||
export const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`;
|
||||
export const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
||||
export const INTEGRATION_VERCEL_TOKEN_URL =
|
||||
"https://api.vercel.com/v2/oauth/access_token";
|
||||
const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
||||
const INTEGRATION_GITHUB_TOKEN_URL =
|
||||
export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
||||
export const INTEGRATION_GITHUB_TOKEN_URL =
|
||||
"https://github.com/login/oauth/access_token";
|
||||
const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
||||
export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
|
||||
|
||||
// integration apps endpoints
|
||||
const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||
const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
||||
const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
||||
const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
||||
const INTEGRATION_RENDER_API_URL = "https://api.render.com";
|
||||
const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2";
|
||||
const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
||||
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||
const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
|
||||
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
||||
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
||||
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
||||
export const INTEGRATION_RENDER_API_URL = "https://api.render.com";
|
||||
export const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2";
|
||||
export const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
||||
export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||
export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||
export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
|
||||
|
||||
const getIntegrationOptions = async () => {
|
||||
export const getIntegrationOptions = async () => {
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: 'Heroku',
|
||||
@ -202,41 +202,4 @@ const getIntegrationOptions = async () => {
|
||||
]
|
||||
|
||||
return INTEGRATION_OPTIONS;
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_OAUTH2,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_HEROKU_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
INTEGRATION_HEROKU_API_URL,
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
INTEGRATION_NETLIFY_API_URL,
|
||||
INTEGRATION_RENDER_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
INTEGRATION_TRAVISCI_API_URL,
|
||||
INTEGRATION_SUPABASE_API_URL,
|
||||
getIntegrationOptions
|
||||
};
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
// membership roles
|
||||
const OWNER = "owner";
|
||||
const ADMIN = "admin";
|
||||
const MEMBER = "member";
|
||||
export const OWNER = "owner";
|
||||
export const ADMIN = "admin";
|
||||
export const MEMBER = "member";
|
||||
|
||||
// membership statuses
|
||||
const INVITED = "invited";
|
||||
export const INVITED = "invited";
|
||||
|
||||
// -- organization
|
||||
const ACCEPTED = "accepted";
|
||||
|
||||
export { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED };
|
||||
export const ACCEPTED = "accepted";
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user