mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 09:26:37 +00:00
Fix merge conflicts
This commit is contained in:
+15
-15
@@ -1,15 +1,13 @@
|
|||||||
# Keys
|
# Keys
|
||||||
# Required keys for platform encryption/decryption ops
|
# Required key for platform encryption/decryption ops
|
||||||
PRIVATE_KEY=replace_with_nacl_sk
|
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
||||||
PUBLIC_KEY=replace_with_nacl_pk
|
|
||||||
ENCRYPTION_KEY=replace_with_lengthy_secure_hex
|
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
# Required secrets to sign JWT tokens
|
# Required secrets to sign JWT tokens
|
||||||
JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex
|
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
|
||||||
JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex
|
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
|
||||||
JWT_AUTH_SECRET=replace_with_lengthy_secure_hex
|
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
|
||||||
JWT_SERVICE_SECRET=replace_with_lengthy_secure_hex
|
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
|
||||||
|
|
||||||
# JWT lifetime
|
# JWT lifetime
|
||||||
# Optional lifetimes for JWT tokens expressed in seconds or a string
|
# Optional lifetimes for JWT tokens expressed in seconds or a string
|
||||||
@@ -33,26 +31,28 @@ MONGO_PASSWORD=example
|
|||||||
|
|
||||||
# Website URL
|
# Website URL
|
||||||
# Required
|
# Required
|
||||||
|
|
||||||
SITE_URL=http://localhost:8080
|
SITE_URL=http://localhost:8080
|
||||||
|
|
||||||
# Mail/SMTP
|
# Mail/SMTP
|
||||||
# Required to send emails
|
SMTP_HOST= # required
|
||||||
# By default, SMTP_HOST is set to smtp.gmail.com
|
SMTP_USERNAME= # required
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_PASSWORD= # required
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_NAME=Team
|
SMTP_SECURE=false
|
||||||
SMTP_USERNAME=[email protected]
|
SMTP_FROM_ADDRESS= # required
|
||||||
SMTP_PASSWORD=
|
SMTP_FROM_NAME=Infisical
|
||||||
|
|
||||||
# Integration
|
# Integration
|
||||||
# Optional only if integration is used
|
# Optional only if integration is used
|
||||||
CLIENT_ID_HEROKU=
|
CLIENT_ID_HEROKU=
|
||||||
CLIENT_ID_VERCEL=
|
CLIENT_ID_VERCEL=
|
||||||
CLIENT_ID_NETLIFY=
|
CLIENT_ID_NETLIFY=
|
||||||
|
CLIENT_ID_GITHUB=
|
||||||
CLIENT_SECRET_HEROKU=
|
CLIENT_SECRET_HEROKU=
|
||||||
CLIENT_SECRET_VERCEL=
|
CLIENT_SECRET_VERCEL=
|
||||||
CLIENT_SECRET_NETLIFY=
|
CLIENT_SECRET_NETLIFY=
|
||||||
|
CLIENT_SECRET_GITHUB=
|
||||||
|
CLIENT_SLUG_VERCEL=
|
||||||
|
|
||||||
# Sentry (optional) for monitoring errors
|
# Sentry (optional) for monitoring errors
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: "Backend Test Report"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Check Backend Pull Request"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
be-report:
|
||||||
|
name: Backend test report
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: 📁 Download test results
|
||||||
|
id: download-artifact
|
||||||
|
uses: dawidd6/action-download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: be-test-results
|
||||||
|
path: backend
|
||||||
|
workflow: check-be-pull-request.yml
|
||||||
|
workflow_conclusion: success
|
||||||
|
- name: 📋 Publish test results
|
||||||
|
uses: dorny/test-reporter@v1
|
||||||
|
with:
|
||||||
|
name: Test Results
|
||||||
|
path: reports/jest-*.xml
|
||||||
|
reporter: jest-junit
|
||||||
|
working-directory: backend
|
||||||
|
- name: 📋 Publish coverage
|
||||||
|
uses: ArtiomTr/jest-coverage-report-action@v2
|
||||||
|
id: coverage
|
||||||
|
with:
|
||||||
|
output: comment, report-markdown
|
||||||
|
coverage-file: coverage/report.json
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
working-directory: backend
|
||||||
|
- uses: marocchino/sticky-pull-request-comment@v2
|
||||||
|
with:
|
||||||
|
message: ${{ steps.coverage.outputs.report }}
|
||||||
@@ -1,41 +1,42 @@
|
|||||||
name: Check Backend Pull Request
|
name: "Check Backend Pull Request"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [ opened, synchronize ]
|
types: [opened, synchronize]
|
||||||
paths:
|
paths:
|
||||||
- 'backend/**'
|
- "backend/**"
|
||||||
- '!backend/README.md'
|
- "!backend/README.md"
|
||||||
- '!backend/.*'
|
- "!backend/.*"
|
||||||
- 'backend/.eslintrc.js'
|
- "backend/.eslintrc.js"
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
check-be-pr:
|
check-be-pr:
|
||||||
name: Check
|
name: Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: ☁️ Checkout source
|
||||||
name: ☁️ Checkout source
|
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
-
|
- name: 🔧 Setup Node 16
|
||||||
name: 🔧 Setup Node 16
|
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: "16"
|
||||||
cache: 'npm'
|
cache: "npm"
|
||||||
cache-dependency-path: backend/package-lock.json
|
cache-dependency-path: backend/package-lock.json
|
||||||
-
|
- name: 📦 Install dependencies
|
||||||
name: 📦 Install dependencies
|
|
||||||
run: npm ci --only-production --ignore-scripts
|
run: npm ci --only-production --ignore-scripts
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
# -
|
- name: 🧪 Run tests
|
||||||
# name: 🧪 Run tests
|
run: npm run test:ci
|
||||||
# run: npm run test:ci
|
working-directory: backend
|
||||||
# working-directory: backend
|
- name: 📁 Upload test results
|
||||||
-
|
uses: actions/upload-artifact@v3
|
||||||
name: 🏗️ Run build
|
if: always()
|
||||||
|
with:
|
||||||
|
name: be-test-results
|
||||||
|
path: |
|
||||||
|
./backend/reports
|
||||||
|
./backend/coverage
|
||||||
|
- name: 🏗️ Run build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
name: Close inactive issues
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "30 1 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
close-issues:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v4
|
|
||||||
with:
|
|
||||||
days-before-issue-stale: 30
|
|
||||||
days-before-issue-close: 14
|
|
||||||
stale-issue-label: "stale"
|
|
||||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
|
||||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
|
||||||
days-before-pr-stale: -1
|
|
||||||
days-before-pr-close: -1
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -13,7 +13,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
goreleaser:
|
goreleaser:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -24,6 +24,15 @@ jobs:
|
|||||||
go-version: '>=1.19.3'
|
go-version: '>=1.19.3'
|
||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: cli/go.sum
|
cache-dependency-path: cli/go.sum
|
||||||
|
- name: libssl1.1 => libssl1.0-dev for OSXCross
|
||||||
|
run: |
|
||||||
|
echo 'deb http://security.ubuntu.com/ubuntu bionic-security main' | sudo tee -a /etc/apt/sources.list
|
||||||
|
sudo apt update && apt-cache policy libssl1.0-dev
|
||||||
|
sudo apt-get install libssl1.0-dev
|
||||||
|
- name: OSXCross for CGO Support
|
||||||
|
run: |
|
||||||
|
mkdir ../../osxcross
|
||||||
|
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
|
||||||
- uses: goreleaser/goreleaser-action@v2
|
- uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
@@ -32,6 +41,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||||
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
||||||
|
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
- run: pip install --upgrade cloudsmith-cli
|
- run: pip install --upgrade cloudsmith-cli
|
||||||
- name: Publish to CloudSmith
|
- name: Publish to CloudSmith
|
||||||
|
|||||||
+3
-1
@@ -25,7 +25,9 @@ node_modules
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
coverage
|
||||||
|
reports
|
||||||
|
junit.xml
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
|
|||||||
+36
-11
@@ -7,28 +7,37 @@
|
|||||||
# # you may remove this if you don't need go generate
|
# # you may remove this if you don't need go generate
|
||||||
# - cd cli && go generate ./...
|
# - cd cli && go generate ./...
|
||||||
builds:
|
builds:
|
||||||
- env:
|
- id: darwin-build
|
||||||
- CGO_ENABLED=0
|
|
||||||
binary: infisical
|
binary: infisical
|
||||||
id: infisical
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=/home/runner/work/osxcross/target/bin/o64-clang
|
||||||
|
- CXX=/home/runner/work/osxcross/target/bin/o64-clang++
|
||||||
goos:
|
goos:
|
||||||
- darwin
|
- darwin
|
||||||
|
ignore:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: "386"
|
||||||
|
dir: ./cli
|
||||||
|
- id: all-other-builds
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
binary: infisical
|
||||||
|
goos:
|
||||||
- freebsd
|
- freebsd
|
||||||
- linux
|
- linux
|
||||||
- netbsd
|
- netbsd
|
||||||
- openbsd
|
- openbsd
|
||||||
- windows
|
- windows
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- "386"
|
||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
goarm:
|
||||||
- 6
|
- "6"
|
||||||
- 7
|
- "7"
|
||||||
ignore:
|
ignore:
|
||||||
- goos: darwin
|
|
||||||
goarch: "386"
|
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: "386"
|
goarch: "386"
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
@@ -71,12 +80,12 @@ nfpms:
|
|||||||
- id: infisical
|
- id: infisical
|
||||||
package_name: infisical
|
package_name: infisical
|
||||||
builds:
|
builds:
|
||||||
- infisical
|
- all-other-builds
|
||||||
vendor: Infisical, Inc
|
vendor: Infisical, Inc
|
||||||
homepage: https://infisical.com/
|
homepage: https://infisical.com/
|
||||||
maintainer: Infisical, Inc
|
maintainer: Infisical, Inc
|
||||||
description: The offical Infisical CLI
|
description: The offical Infisical CLI
|
||||||
license: Apache 2.0
|
license: MIT
|
||||||
formats:
|
formats:
|
||||||
- rpm
|
- rpm
|
||||||
- deb
|
- deb
|
||||||
@@ -92,7 +101,23 @@ scoop:
|
|||||||
email: [email protected]
|
email: [email protected]
|
||||||
homepage: "https://infisical.com"
|
homepage: "https://infisical.com"
|
||||||
description: "The official Infisical CLI"
|
description: "The official Infisical CLI"
|
||||||
license: Apache-2.0
|
license: MIT
|
||||||
|
aurs:
|
||||||
|
-
|
||||||
|
name: infisical-bin
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
maintainers:
|
||||||
|
- Infisical, Inc <[email protected]>
|
||||||
|
license: MIT
|
||||||
|
private_key: '{{ .Env.AUR_KEY }}'
|
||||||
|
git_url: 'ssh://[email protected]/infisical-bin.git'
|
||||||
|
package: |-
|
||||||
|
# bin
|
||||||
|
install -Dm755 "./infisical" "${pkgdir}/usr/bin/infisical"
|
||||||
|
# license
|
||||||
|
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/infisical/LICENSE"
|
||||||
|
|
||||||
# dockers:
|
# dockers:
|
||||||
# - dockerfile: goreleaser.dockerfile
|
# - dockerfile: goreleaser.dockerfile
|
||||||
# goos: linux
|
# goos: linux
|
||||||
|
|||||||
@@ -146,7 +146,9 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
🔜 AWS
|
🔜 AWS
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 GitHub Actions (https://github.com/Infisical/infisical/issues/54)
|
<a href="https://infisical.com/docs/integrations/cicd/githubactions">
|
||||||
|
✔️ GitHub Actions
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Railway
|
🔜 Railway
|
||||||
@@ -179,7 +181,9 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
🔜 TravisCI
|
🔜 TravisCI
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Netlify (https://github.com/Infisical/infisical/issues/55)
|
<a href="https://infisical.com/docs/integrations/cloud/netlify">
|
||||||
|
✔️ Netlify
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Railway
|
🔜 Railway
|
||||||
@@ -317,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
|
|
||||||
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { server } from '../src/app';
|
||||||
|
import { describe, expect, it, beforeAll, afterAll } from '@jest/globals';
|
||||||
|
import supertest from 'supertest';
|
||||||
|
import { setUpHealthEndpoint } from '../src/services/health';
|
||||||
|
|
||||||
|
const requestWithSupertest = supertest(server);
|
||||||
|
describe('Healthcheck endpoint', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
setUpHealthEndpoint(server);
|
||||||
|
});
|
||||||
|
afterAll(async () => {
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('GET /healthcheck should return OK', async () => {
|
||||||
|
const res = await requestWithSupertest.get('/healthcheck');
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
Vendored
+2
-2
@@ -14,6 +14,8 @@ declare global {
|
|||||||
JWT_SIGNUP_SECRET: string;
|
JWT_SIGNUP_SECRET: string;
|
||||||
MONGO_URL: string;
|
MONGO_URL: string;
|
||||||
NODE_ENV: 'development' | 'staging' | 'testing' | 'production';
|
NODE_ENV: 'development' | 'staging' | 'testing' | 'production';
|
||||||
|
VERBOSE_ERROR_OUTPUT: string;
|
||||||
|
LOKI_HOST: string;
|
||||||
CLIENT_ID_HEROKU: string;
|
CLIENT_ID_HEROKU: string;
|
||||||
CLIENT_ID_VERCEL: string;
|
CLIENT_ID_VERCEL: string;
|
||||||
CLIENT_ID_NETLIFY: string;
|
CLIENT_ID_NETLIFY: string;
|
||||||
@@ -22,8 +24,6 @@ declare global {
|
|||||||
CLIENT_SECRET_NETLIFY: string;
|
CLIENT_SECRET_NETLIFY: string;
|
||||||
POSTHOG_HOST: string;
|
POSTHOG_HOST: string;
|
||||||
POSTHOG_PROJECT_API_KEY: string;
|
POSTHOG_PROJECT_API_KEY: string;
|
||||||
PRIVATE_KEY: string;
|
|
||||||
PUBLIC_KEY: string;
|
|
||||||
SENTRY_DSN: string;
|
SENTRY_DSN: string;
|
||||||
SITE_URL: string;
|
SITE_URL: string;
|
||||||
SMTP_HOST: string;
|
SMTP_HOST: string;
|
||||||
|
|||||||
Generated
+1538
-261
File diff suppressed because it is too large
Load Diff
+40
-4
@@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@godaddy/terminus": "^4.11.2",
|
"@godaddy/terminus": "^4.11.2",
|
||||||
|
"@octokit/rest": "^19.0.5",
|
||||||
"@sentry/node": "^7.14.0",
|
"@sentry/node": "^7.14.0",
|
||||||
"@sentry/tracing": "^7.19.0",
|
"@sentry/tracing": "^7.19.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
|
"@types/libsodium-wrappers": "^0.7.10",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
"bigint-conversion": "^2.2.2",
|
"bigint-conversion": "^2.2.2",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
@@ -15,17 +17,20 @@
|
|||||||
"express-validator": "^6.14.2",
|
"express-validator": "^6.14.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"helmet": "^5.1.1",
|
"helmet": "^5.1.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
|
"libsodium-wrappers": "^0.7.10",
|
||||||
"mongoose": "^6.7.2",
|
"mongoose": "^6.7.2",
|
||||||
"nodemailer": "^6.8.0",
|
"nodemailer": "^6.8.0",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.2.2",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"stripe": "^10.7.0",
|
"stripe": "^10.7.0",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"tweetnacl-util": "^0.15.1",
|
"tweetnacl-util": "^0.15.1",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^4.9.3",
|
||||||
|
"winston": "^3.8.2",
|
||||||
|
"winston-loki": "^6.0.6"
|
||||||
},
|
},
|
||||||
"name": "infisical-api",
|
"name": "infisical-api",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -37,7 +42,11 @@
|
|||||||
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
|
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
"lint-and-fix": "eslint . --ext .ts --fix",
|
"lint-and-fix": "eslint . --ext .ts --fix",
|
||||||
"lint-staged": "lint-staged"
|
"lint-staged": "lint-staged",
|
||||||
|
"pretest": "docker compose -f test-resources/docker-compose.test.yml up -d",
|
||||||
|
"test": "cross-env NODE_ENV=test jest --testTimeout=10000 --detectOpenHandles",
|
||||||
|
"test:ci": "npm test -- --watchAll=false --ci --reporters=default --reporters=jest-junit --reporters=github-actions --coverage --testLocationInResults --json --outputFile=coverage/report.json",
|
||||||
|
"posttest": "docker compose -f test-resources/docker-compose.test.yml down"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -51,22 +60,49 @@
|
|||||||
"homepage": "https://github.com/Infisical/infisical-api#readme",
|
"homepage": "https://github.com/Infisical/infisical-api#readme",
|
||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@jest/globals": "^29.3.1",
|
||||||
"@posthog/plugin-scaffold": "^1.3.4",
|
"@posthog/plugin-scaffold": "^1.3.4",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
|
"@types/jest": "^29.2.4",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
"@types/node": "^18.11.3",
|
"@types/node": "^18.11.3",
|
||||||
"@types/nodemailer": "^6.4.6",
|
"@types/nodemailer": "^6.4.6",
|
||||||
|
"@types/supertest": "^2.0.12",
|
||||||
"@types/swagger-jsdoc": "^6.0.1",
|
"@types/swagger-jsdoc": "^6.0.1",
|
||||||
"@types/swagger-ui-express": "^4.1.3",
|
"@types/swagger-ui-express": "^4.1.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||||
"@typescript-eslint/parser": "^5.40.1",
|
"@typescript-eslint/parser": "^5.40.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.26.0",
|
"eslint": "^8.26.0",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"jest": "^29.3.1",
|
"jest": "^29.3.1",
|
||||||
|
"jest-junit": "^15.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.19",
|
||||||
"npm": "^8.19.3",
|
"npm": "^8.19.3",
|
||||||
|
"supertest": "^6.3.3",
|
||||||
|
"ts-jest": "^29.0.3",
|
||||||
"ts-node": "^10.9.1"
|
"ts-node": "^10.9.1"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"src/*.{js,ts}",
|
||||||
|
"!**/node_modules/**"
|
||||||
|
],
|
||||||
|
"setupFiles": [
|
||||||
|
"<rootDir>/test-resources/env-vars.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"jest-junit": {
|
||||||
|
"outputDirectory": "reports",
|
||||||
|
"outputName": "jest-junit.xml",
|
||||||
|
"ancestorSeparator": " › ",
|
||||||
|
"uniqueOutputName": "false",
|
||||||
|
"suiteNameTemplate": "{filepath}",
|
||||||
|
"classNameTemplate": "{classname}",
|
||||||
|
"titleTemplate": "{title}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { patchRouterParam } = require('./utils/patchAsyncRoutes');
|
||||||
|
|
||||||
|
import express from 'express';
|
||||||
|
import helmet from 'helmet';
|
||||||
|
import cors from 'cors';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
import { PORT, NODE_ENV, SITE_URL } from './config';
|
||||||
|
import { apiLimiter } from './helpers/rateLimiter';
|
||||||
|
|
||||||
|
import {
|
||||||
|
workspace as eeWorkspaceRouter,
|
||||||
|
secret as eeSecretRouter
|
||||||
|
} from './ee/routes/v1';
|
||||||
|
import {
|
||||||
|
signup as v1SignupRouter,
|
||||||
|
auth as v1AuthRouter,
|
||||||
|
bot as v1BotRouter,
|
||||||
|
organization as v1OrganizationRouter,
|
||||||
|
workspace as v1WorkspaceRouter,
|
||||||
|
membershipOrg as v1MembershipOrgRouter,
|
||||||
|
membership as v1MembershipRouter,
|
||||||
|
key as v1KeyRouter,
|
||||||
|
inviteOrg as v1InviteOrgRouter,
|
||||||
|
user as v1UserRouter,
|
||||||
|
userAction as v1UserActionRouter,
|
||||||
|
secret as v1SecretRouter,
|
||||||
|
serviceToken as v1ServiceTokenRouter,
|
||||||
|
password as v1PasswordRouter,
|
||||||
|
stripe as v1StripeRouter,
|
||||||
|
integration as v1IntegrationRouter,
|
||||||
|
integrationAuth as v1IntegrationAuthRouter
|
||||||
|
} from './routes/v1';
|
||||||
|
import {
|
||||||
|
secret as v2SecretRouter,
|
||||||
|
workspace as v2WorkspaceRouter
|
||||||
|
} from './routes/v2';
|
||||||
|
|
||||||
|
import { getLogger } from './utils/logger';
|
||||||
|
import { RouteNotFoundError } from './utils/errors';
|
||||||
|
import { requestErrorHandler } from './middleware/requestErrorHandler';
|
||||||
|
|
||||||
|
// patch async route params to handle Promise Rejections
|
||||||
|
patchRouterParam();
|
||||||
|
|
||||||
|
export const app = express();
|
||||||
|
|
||||||
|
app.enable('trust proxy');
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(
|
||||||
|
cors({
|
||||||
|
credentials: true,
|
||||||
|
origin: SITE_URL
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (NODE_ENV === 'production') {
|
||||||
|
// enable app-wide rate-limiting + helmet security
|
||||||
|
// in production
|
||||||
|
app.disable('x-powered-by');
|
||||||
|
app.use(apiLimiter);
|
||||||
|
app.use(helmet());
|
||||||
|
}
|
||||||
|
|
||||||
|
// (EE) routes
|
||||||
|
app.use('/api/v1/secret', eeSecretRouter);
|
||||||
|
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
||||||
|
|
||||||
|
// v1 routes
|
||||||
|
app.use('/api/v1/signup', v1SignupRouter);
|
||||||
|
app.use('/api/v1/auth', v1AuthRouter);
|
||||||
|
app.use('/api/v1/bot', v1BotRouter);
|
||||||
|
app.use('/api/v1/user', v1UserRouter);
|
||||||
|
app.use('/api/v1/user-action', v1UserActionRouter);
|
||||||
|
app.use('/api/v1/organization', v1OrganizationRouter);
|
||||||
|
app.use('/api/v1/workspace', v1WorkspaceRouter);
|
||||||
|
app.use('/api/v1/membership-org', v1MembershipOrgRouter);
|
||||||
|
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);
|
||||||
|
app.use('/api/v1/password', v1PasswordRouter);
|
||||||
|
app.use('/api/v1/stripe', v1StripeRouter);
|
||||||
|
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||||
|
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||||
|
|
||||||
|
// v2 routes
|
||||||
|
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||||
|
app.use('/api/v2/secret', v2SecretRouter);
|
||||||
|
|
||||||
|
|
||||||
|
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||||
|
app.use((req, res, next)=>{
|
||||||
|
if(res.headersSent) return next();
|
||||||
|
next(RouteNotFoundError({message: `The requested source '(${req.method})${req.url}' was not found`}))
|
||||||
|
})
|
||||||
|
|
||||||
|
//* Error Handling Middleware (must be after all routing logic)
|
||||||
|
app.use(requestErrorHandler)
|
||||||
|
|
||||||
|
|
||||||
|
export const server = app.listen(PORT, () => {
|
||||||
|
getLogger("backend-main").info(`Server started listening at port ${PORT}`)
|
||||||
|
});
|
||||||
@@ -10,26 +10,30 @@ const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m';
|
|||||||
const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!;
|
const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!;
|
||||||
const MONGO_URL = process.env.MONGO_URL!;
|
const MONGO_URL = process.env.MONGO_URL!;
|
||||||
const NODE_ENV = process.env.NODE_ENV! || 'production';
|
const NODE_ENV = process.env.NODE_ENV! || 'production';
|
||||||
|
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
|
||||||
|
const LOKI_HOST = process.env.LOKI_HOST || undefined;
|
||||||
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
|
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
|
||||||
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
|
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
|
||||||
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
|
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
|
||||||
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
|
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
|
||||||
|
const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!;
|
||||||
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
|
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
|
||||||
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
|
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
|
||||||
|
const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!;
|
||||||
const CLIENT_SLUG_VERCEL= process.env.CLIENT_SLUG_VERCEL!;
|
const CLIENT_SLUG_VERCEL= process.env.CLIENT_SLUG_VERCEL!;
|
||||||
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
|
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
|
||||||
const POSTHOG_PROJECT_API_KEY =
|
const POSTHOG_PROJECT_API_KEY =
|
||||||
process.env.POSTHOG_PROJECT_API_KEY! ||
|
process.env.POSTHOG_PROJECT_API_KEY! ||
|
||||||
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
|
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
|
||||||
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
|
|
||||||
const PUBLIC_KEY = process.env.PUBLIC_KEY!;
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN!;
|
const SENTRY_DSN = process.env.SENTRY_DSN!;
|
||||||
const SITE_URL = process.env.SITE_URL!;
|
const SITE_URL = process.env.SITE_URL!;
|
||||||
const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com';
|
const SMTP_HOST = process.env.SMTP_HOST!;
|
||||||
const SMTP_PORT = process.env.SMTP_PORT! || 587;
|
const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false;
|
||||||
const SMTP_NAME = process.env.SMTP_NAME!;
|
const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587;
|
||||||
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
|
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
|
||||||
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
|
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
|
||||||
|
const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!;
|
||||||
|
const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical';
|
||||||
const STRIPE_PRODUCT_CARD_AUTH = process.env.STRIPE_PRODUCT_CARD_AUTH!;
|
const STRIPE_PRODUCT_CARD_AUTH = process.env.STRIPE_PRODUCT_CARD_AUTH!;
|
||||||
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
|
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
|
||||||
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
|
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
|
||||||
@@ -37,6 +41,7 @@ const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!;
|
|||||||
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!;
|
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!;
|
||||||
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
|
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
|
||||||
const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true;
|
const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true;
|
||||||
|
const LICENSE_KEY = process.env.LICENSE_KEY!;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PORT,
|
PORT,
|
||||||
@@ -51,29 +56,34 @@ export {
|
|||||||
JWT_SIGNUP_SECRET,
|
JWT_SIGNUP_SECRET,
|
||||||
MONGO_URL,
|
MONGO_URL,
|
||||||
NODE_ENV,
|
NODE_ENV,
|
||||||
|
VERBOSE_ERROR_OUTPUT,
|
||||||
|
LOKI_HOST,
|
||||||
CLIENT_ID_HEROKU,
|
CLIENT_ID_HEROKU,
|
||||||
CLIENT_ID_VERCEL,
|
CLIENT_ID_VERCEL,
|
||||||
CLIENT_ID_NETLIFY,
|
CLIENT_ID_NETLIFY,
|
||||||
|
CLIENT_ID_GITHUB,
|
||||||
CLIENT_SECRET_HEROKU,
|
CLIENT_SECRET_HEROKU,
|
||||||
CLIENT_SECRET_VERCEL,
|
CLIENT_SECRET_VERCEL,
|
||||||
CLIENT_SECRET_NETLIFY,
|
CLIENT_SECRET_NETLIFY,
|
||||||
|
CLIENT_SECRET_GITHUB,
|
||||||
CLIENT_SLUG_VERCEL,
|
CLIENT_SLUG_VERCEL,
|
||||||
POSTHOG_HOST,
|
POSTHOG_HOST,
|
||||||
POSTHOG_PROJECT_API_KEY,
|
POSTHOG_PROJECT_API_KEY,
|
||||||
PRIVATE_KEY,
|
|
||||||
PUBLIC_KEY,
|
|
||||||
SENTRY_DSN,
|
SENTRY_DSN,
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
SMTP_HOST,
|
SMTP_HOST,
|
||||||
SMTP_PORT,
|
SMTP_PORT,
|
||||||
SMTP_NAME,
|
SMTP_SECURE,
|
||||||
SMTP_USERNAME,
|
SMTP_USERNAME,
|
||||||
SMTP_PASSWORD,
|
SMTP_PASSWORD,
|
||||||
|
SMTP_FROM_ADDRESS,
|
||||||
|
SMTP_FROM_NAME,
|
||||||
STRIPE_PRODUCT_CARD_AUTH,
|
STRIPE_PRODUCT_CARD_AUTH,
|
||||||
STRIPE_PRODUCT_PRO,
|
STRIPE_PRODUCT_PRO,
|
||||||
STRIPE_PRODUCT_STARTER,
|
STRIPE_PRODUCT_STARTER,
|
||||||
STRIPE_PUBLISHABLE_KEY,
|
STRIPE_PUBLISHABLE_KEY,
|
||||||
STRIPE_SECRET_KEY,
|
STRIPE_SECRET_KEY,
|
||||||
STRIPE_WEBHOOK_SECRET,
|
STRIPE_WEBHOOK_SECRET,
|
||||||
TELEMETRY_ENABLED
|
TELEMETRY_ENABLED,
|
||||||
|
LICENSE_KEY
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-3
@@ -4,14 +4,14 @@ import jwt from 'jsonwebtoken';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
import { User } from '../models';
|
import { User } from '../../models';
|
||||||
import { createToken, issueTokens, clearTokens } from '../helpers/auth';
|
import { createToken, issueTokens, clearTokens } from '../../helpers/auth';
|
||||||
import {
|
import {
|
||||||
NODE_ENV,
|
NODE_ENV,
|
||||||
JWT_AUTH_LIFETIME,
|
JWT_AUTH_LIFETIME,
|
||||||
JWT_AUTH_SECRET,
|
JWT_AUTH_SECRET,
|
||||||
JWT_REFRESH_SECRET
|
JWT_REFRESH_SECRET
|
||||||
} from '../config';
|
} from '../../config';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Bot, BotKey } from '../models';
|
import { Bot, BotKey } from '../../models';
|
||||||
import { createBot } from '../helpers/bot';
|
import { createBot } from '../../helpers/bot';
|
||||||
|
|
||||||
interface BotKey {
|
interface BotKey {
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
+4
-4
@@ -2,10 +2,10 @@ import { Request, Response } from 'express';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { IntegrationAuth, Integration } from '../models';
|
import { IntegrationAuth, Integration } from '../../models';
|
||||||
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../variables';
|
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../../variables';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../../services';
|
||||||
import { getApps, revokeAccess } from '../integrations';
|
import { getApps, revokeAccess } from '../../integrations';
|
||||||
|
|
||||||
export const getIntegrationOptions = async (
|
export const getIntegrationOptions = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Integration, Bot, BotKey } from '../models';
|
import { Integration, Bot, BotKey } from '../../models';
|
||||||
import { EventService } from '../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
|
|
||||||
interface Key {
|
interface Key {
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
+4
-17
@@ -1,9 +1,8 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Key } from '../models';
|
import { Key } from '../../models';
|
||||||
import { findMembership } from '../helpers/membership';
|
import { findMembership } from '../../helpers/membership';
|
||||||
import { PUBLIC_KEY } from '../config';
|
import { GRANTED } from '../../variables';
|
||||||
import { GRANTED } from '../variables';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
|
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
|
||||||
@@ -84,16 +83,4 @@ export const getLatestKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send(resObj);
|
return res.status(200).send(resObj);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return public key of Infisical
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getPublicKeyInfisical = async (req: Request, res: Response) => {
|
|
||||||
return res.status(200).send({
|
|
||||||
publicKey: PUBLIC_KEY
|
|
||||||
});
|
|
||||||
};
|
|
||||||
+5
-5
@@ -1,13 +1,13 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Membership, MembershipOrg, User, Key } from '../models';
|
import { Membership, MembershipOrg, User, Key } from '../../models';
|
||||||
import {
|
import {
|
||||||
findMembership,
|
findMembership,
|
||||||
deleteMembership as deleteMember
|
deleteMembership as deleteMember
|
||||||
} from '../helpers/membership';
|
} from '../../helpers/membership';
|
||||||
import { sendMail } from '../helpers/nodemailer';
|
import { sendMail } from '../../helpers/nodemailer';
|
||||||
import { SITE_URL } from '../config';
|
import { SITE_URL } from '../../config';
|
||||||
import { ADMIN, MEMBER, GRANTED, ACCEPTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED, ACCEPTED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that user is a member of workspace with id [workspaceId]
|
* Check that user is a member of workspace with id [workspaceId]
|
||||||
+16
-16
@@ -1,14 +1,14 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../config';
|
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
|
||||||
import { MembershipOrg, Organization, User, Token } from '../models';
|
import { MembershipOrg, Organization, User, Token } from '../../models';
|
||||||
import { deleteMembershipOrg as deleteMemberFromOrg } from '../helpers/membershipOrg';
|
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg';
|
||||||
import { checkEmailVerification } from '../helpers/signup';
|
import { checkEmailVerification } from '../../helpers/signup';
|
||||||
import { createToken } from '../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
import { updateSubscriptionOrgQuantity } from '../helpers/organization';
|
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
||||||
import { sendMail } from '../helpers/nodemailer';
|
import { sendMail } from '../../helpers/nodemailer';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED } from '../variables';
|
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete organization membership with id [membershipOrgId] from organization
|
* Delete organization membership with id [membershipOrgId] from organization
|
||||||
@@ -80,14 +80,14 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
|||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
let membershipToChangeRole;
|
let membershipToChangeRole;
|
||||||
try {
|
// try {
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
// Sentry.setUser({ email: req.user.email });
|
||||||
Sentry.captureException(err);
|
// Sentry.captureException(err);
|
||||||
return res.status(400).send({
|
// return res.status(400).send({
|
||||||
message: 'Failed to change organization membership role'
|
// message: 'Failed to change organization membership role'
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membershipOrg: membershipToChangeRole
|
membershipOrg: membershipToChangeRole
|
||||||
+5
-5
@@ -6,7 +6,7 @@ import {
|
|||||||
STRIPE_PRODUCT_STARTER,
|
STRIPE_PRODUCT_STARTER,
|
||||||
STRIPE_PRODUCT_PRO,
|
STRIPE_PRODUCT_PRO,
|
||||||
STRIPE_PRODUCT_CARD_AUTH
|
STRIPE_PRODUCT_CARD_AUTH
|
||||||
} from '../config';
|
} from '../../config';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
||||||
@@ -18,10 +18,10 @@ import {
|
|||||||
Organization,
|
Organization,
|
||||||
Workspace,
|
Workspace,
|
||||||
IncidentContactOrg
|
IncidentContactOrg
|
||||||
} from '../models';
|
} from '../../models';
|
||||||
import { createOrganization as create } from '../helpers/organization';
|
import { createOrganization as create } from '../../helpers/organization';
|
||||||
import { addMembershipsOrg } from '../helpers/membershipOrg';
|
import { addMembershipsOrg } from '../../helpers/membershipOrg';
|
||||||
import { OWNER, ACCEPTED } from '../variables';
|
import { OWNER, ACCEPTED } from '../../variables';
|
||||||
|
|
||||||
const productToPriceMap = {
|
const productToPriceMap = {
|
||||||
starter: STRIPE_PRODUCT_STARTER,
|
starter: STRIPE_PRODUCT_STARTER,
|
||||||
+6
-5
@@ -1,13 +1,14 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
import { User, Token, BackupPrivateKey } from '../models';
|
import { User, Token, BackupPrivateKey } from '../../models';
|
||||||
import { checkEmailVerification } from '../helpers/signup';
|
import { checkEmailVerification } from '../../helpers/signup';
|
||||||
import { createToken } from '../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
import { sendMail } from '../helpers/nodemailer';
|
import { sendMail } from '../../helpers/nodemailer';
|
||||||
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../config';
|
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
|
||||||
|
|
||||||
const clientPublicKeys: any = {};
|
const clientPublicKeys: any = {};
|
||||||
|
|
||||||
+13
-12
@@ -1,16 +1,16 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Key } from '../models';
|
import { Key, Secret } from '../../models';
|
||||||
import {
|
import {
|
||||||
pushSecrets as push,
|
v1PushSecrets as push,
|
||||||
pullSecrets as pull,
|
pullSecrets as pull,
|
||||||
reformatPullSecrets
|
reformatPullSecrets
|
||||||
} from '../helpers/secret';
|
} from '../../helpers/secret';
|
||||||
import { pushKeys } from '../helpers/key';
|
import { pushKeys } from '../../helpers/key';
|
||||||
import { eventPushSecrets } from '../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EventService } from '../services';
|
import { EventService } from '../../services';
|
||||||
import { ENV_SET } from '../variables';
|
import { ENV_SET } from '../../variables';
|
||||||
import { postHogClient } from '../services';
|
import { postHogClient } from '../../services';
|
||||||
|
|
||||||
interface PushSecret {
|
interface PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
@@ -21,6 +21,10 @@ interface PushSecret {
|
|||||||
ivValue: string;
|
ivValue: string;
|
||||||
tagValue: string;
|
tagValue: string;
|
||||||
hashValue: string;
|
hashValue: string;
|
||||||
|
ciphertextComment: string;
|
||||||
|
ivComment: string;
|
||||||
|
tagComment: string;
|
||||||
|
hashComment: string;
|
||||||
type: 'shared' | 'personal';
|
type: 'shared' | 'personal';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,9 +173,6 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||||
// get (encrypted) secrets from workspace with id [workspaceId]
|
|
||||||
// service token route
|
|
||||||
|
|
||||||
let secrets;
|
let secrets;
|
||||||
let key;
|
let key;
|
||||||
try {
|
try {
|
||||||
@@ -225,4 +226,4 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
|||||||
secrets: reformatPullSecrets({ secrets }),
|
secrets: reformatPullSecrets({ secrets }),
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
+6
-5
@@ -1,8 +1,8 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { ServiceToken } from '../models';
|
import { ServiceToken } from '../../models';
|
||||||
import { createToken } from '../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
import { ENV_SET } from '../variables';
|
import { ENV_SET } from '../../variables';
|
||||||
import { JWT_SERVICE_SECRET } from '../config';
|
import { JWT_SERVICE_SECRET } from '../../config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return service token on request
|
* Return service token on request
|
||||||
@@ -58,7 +58,8 @@ export const createServiceToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
token = createToken({
|
token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
serviceTokenId: serviceToken._id.toString()
|
serviceTokenId: serviceToken._id.toString(),
|
||||||
|
workspaceId
|
||||||
},
|
},
|
||||||
expiresIn: expiresIn,
|
expiresIn: expiresIn,
|
||||||
secret: JWT_SERVICE_SECRET
|
secret: JWT_SERVICE_SECRET
|
||||||
+22
-6
@@ -1,15 +1,16 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { NODE_ENV, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../config';
|
import { NODE_ENV, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
|
||||||
import { User, MembershipOrg } from '../models';
|
import { User, MembershipOrg } from '../../models';
|
||||||
import { completeAccount } from '../helpers/user';
|
import { completeAccount } from '../../helpers/user';
|
||||||
import {
|
import {
|
||||||
sendEmailVerification,
|
sendEmailVerification,
|
||||||
checkEmailVerification,
|
checkEmailVerification,
|
||||||
initializeDefaultOrg
|
initializeDefaultOrg
|
||||||
} from '../helpers/signup';
|
} from '../../helpers/signup';
|
||||||
import { issueTokens, createToken } from '../helpers/auth';
|
import { issueTokens, createToken } from '../../helpers/auth';
|
||||||
import { INVITED, ACCEPTED } from '../variables';
|
import { INVITED, ACCEPTED } from '../../variables';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signup step 1: Initialize account for user under email [email] and send a verification code
|
* Signup step 1: Initialize account for user under email [email] and send a verification code
|
||||||
@@ -179,6 +180,21 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
token = tokens.token;
|
token = tokens.token;
|
||||||
refreshToken = tokens.refreshToken;
|
refreshToken = tokens.refreshToken;
|
||||||
|
|
||||||
|
// sending a welcome email to new users
|
||||||
|
if (process.env.LOOPS_API_KEY) {
|
||||||
|
await axios.post("https://app.loops.so/api/v1/events/send", {
|
||||||
|
"email": email,
|
||||||
|
"eventName": "Sign Up",
|
||||||
|
"firstName": firstName,
|
||||||
|
"lastName": lastName
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Authorization": "Bearer " + process.env.LOOPS_API_KEY
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { UserAction } from '../models';
|
import { UserAction } from '../../models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add user action [action]
|
* Add user action [action]
|
||||||
+5
-5
@@ -7,14 +7,14 @@ import {
|
|||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
IUser,
|
IUser,
|
||||||
ServiceToken
|
ServiceToken,
|
||||||
} from '../models';
|
} from '../../models';
|
||||||
import {
|
import {
|
||||||
createWorkspace as create,
|
createWorkspace as create,
|
||||||
deleteWorkspace as deleteWork
|
deleteWorkspace as deleteWork
|
||||||
} from '../helpers/workspace';
|
} from '../../helpers/workspace';
|
||||||
import { addMemberships } from '../helpers/membership';
|
import { addMemberships } from '../../helpers/membership';
|
||||||
import { ADMIN, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return public keys of members of workspace with id [workspaceId]
|
* Return public keys of members of workspace with id [workspaceId]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import * as workspaceController from './workspaceController';
|
||||||
|
|
||||||
|
export {
|
||||||
|
workspaceController
|
||||||
|
}
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import {
|
||||||
|
Workspace,
|
||||||
|
Membership,
|
||||||
|
MembershipOrg,
|
||||||
|
Integration,
|
||||||
|
IntegrationAuth,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
ServiceToken,
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
createWorkspace as create,
|
||||||
|
deleteWorkspace as deleteWork
|
||||||
|
} from '../../helpers/workspace';
|
||||||
|
import {
|
||||||
|
v2PushSecrets as push,
|
||||||
|
pullSecrets as pull,
|
||||||
|
reformatPullSecrets
|
||||||
|
} from '../../helpers/secret';
|
||||||
|
import { pushKeys } from '../../helpers/key';
|
||||||
|
import { addMemberships } from '../../helpers/membership';
|
||||||
|
import { postHogClient, EventService } from '../../services';
|
||||||
|
import { eventPushSecrets } from '../../events';
|
||||||
|
import { ADMIN, COMPLETED, GRANTED, ENV_SET } from '../../variables';
|
||||||
|
interface V2PushSecret {
|
||||||
|
type: string; // personal or shared
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretKeyHash: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretValueHash: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
secretCommentHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return public keys of members of workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
||||||
|
let publicKeys;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
publicKeys = (
|
||||||
|
await Membership.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
}).populate<{ user: IUser }>('user', 'publicKey')
|
||||||
|
)
|
||||||
|
.filter((m) => m.status === COMPLETED || m.status === GRANTED)
|
||||||
|
.map((member) => {
|
||||||
|
return {
|
||||||
|
publicKey: member.user.publicKey,
|
||||||
|
userId: member.user._id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace member public keys'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
publicKeys
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return memberships for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||||
|
let users;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
users = await Membership.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
}).populate('user', '+publicKey');
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace members'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
users
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return workspaces that user is part of
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaces = async (req: Request, res: Response) => {
|
||||||
|
let workspaces;
|
||||||
|
try {
|
||||||
|
workspaces = (
|
||||||
|
await Membership.find({
|
||||||
|
user: req.user._id
|
||||||
|
}).populate('workspace')
|
||||||
|
).map((m) => m.workspace);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspaces'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspaces
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspace = async (req: Request, res: Response) => {
|
||||||
|
let workspace;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
workspace = await Workspace.findOne({
|
||||||
|
_id: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new workspace named [workspaceName] under organization with id
|
||||||
|
* [organizationId] and add user as admin
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createWorkspace = async (req: Request, res: Response) => {
|
||||||
|
let workspace;
|
||||||
|
try {
|
||||||
|
const { workspaceName, organizationId } = req.body;
|
||||||
|
|
||||||
|
// validate organization membership
|
||||||
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
|
user: req.user._id,
|
||||||
|
organization: organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!membershipOrg) {
|
||||||
|
throw new Error('Failed to validate organization membership');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceName.length < 1) {
|
||||||
|
throw new Error('Workspace names must be at least 1-character long');
|
||||||
|
}
|
||||||
|
|
||||||
|
// create workspace and add user as member
|
||||||
|
workspace = await create({
|
||||||
|
name: workspaceName,
|
||||||
|
organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
await addMemberships({
|
||||||
|
userIds: [req.user._id],
|
||||||
|
workspaceId: workspace._id.toString(),
|
||||||
|
roles: [ADMIN],
|
||||||
|
statuses: [GRANTED]
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to create workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteWorkspace = async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// delete workspace
|
||||||
|
await deleteWork({
|
||||||
|
id: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to delete workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully deleted workspace'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change name of workspace with id [workspaceId] to [name]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const changeWorkspaceName = async (req: Request, res: Response) => {
|
||||||
|
let workspace;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
const { name } = req.body;
|
||||||
|
|
||||||
|
workspace = await Workspace.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: workspaceId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to change workspace name'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully changed workspace name',
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return integrations for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
||||||
|
let integrations;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
integrations = await Integration.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace integrations'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
integrations
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (integration) authorizations for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceIntegrationAuthorizations = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
let authorizations;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
authorizations = await IntegrationAuth.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace integration authorizations'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
authorizations
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service service tokens for workspace [workspaceId] belonging to user
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceServiceTokens = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => {
|
||||||
|
let serviceTokens;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
serviceTokens = await ServiceToken.find({
|
||||||
|
user: req.user._id,
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace service tokens'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceTokens
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
* for environment [environment]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
|
||||||
|
try {
|
||||||
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
|
const { keys, environment, channel } = req.body;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize secrets
|
||||||
|
secrets = secrets.filter(
|
||||||
|
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
|
||||||
|
);
|
||||||
|
|
||||||
|
await push({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secrets
|
||||||
|
});
|
||||||
|
|
||||||
|
await pushKeys({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId,
|
||||||
|
keys
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets pushed',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger event - push secrets
|
||||||
|
EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to upload workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully uploaded workspace secrets'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (encrypted) secrets for workspace with id [workspaceId]
|
||||||
|
* for environment [environment] and (encrypted) workspace key
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
|
// TODO: only return secrets, do not return workspace key
|
||||||
|
|
||||||
|
let secrets;
|
||||||
|
let key;
|
||||||
|
try {
|
||||||
|
const environment: string = req.query.environment as string;
|
||||||
|
const channel: string = req.query.channel as string;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = await pull({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
key = await Key.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
receiver: req.user._id
|
||||||
|
})
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.populate('sender', '+publicKey');
|
||||||
|
|
||||||
|
if (channel !== 'cli') {
|
||||||
|
secrets = reformatPullSecrets({ secrets });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
// capture secrets pushed event in production
|
||||||
|
postHogClient.capture({
|
||||||
|
distinctId: req.user.email,
|
||||||
|
event: 'secrets pulled',
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to pull workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: modify based on upcoming serviceTokenData changes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (encrypted) secrets for workspace with id [workspaceId]
|
||||||
|
* for environment [environment] and (encrypted) workspace key
|
||||||
|
* via service token
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||||
|
let secrets;
|
||||||
|
let key;
|
||||||
|
try {
|
||||||
|
const environment: string = req.query.environment as string;
|
||||||
|
const channel: string = req.query.channel as string;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = await pull({
|
||||||
|
userId: req.serviceToken.user._id.toString(),
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
key = {
|
||||||
|
encryptedKey: req.serviceToken.encryptedKey,
|
||||||
|
nonce: req.serviceToken.nonce,
|
||||||
|
sender: {
|
||||||
|
publicKey: req.serviceToken.publicKey
|
||||||
|
},
|
||||||
|
receiver: req.serviceToken.user,
|
||||||
|
workspace: req.serviceToken.workspace
|
||||||
|
};
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
// capture secrets pulled event in production
|
||||||
|
postHogClient.capture({
|
||||||
|
distinctId: req.serviceToken.user.email,
|
||||||
|
event: 'secrets pulled',
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.serviceToken.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to pull workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets: reformatPullSecrets({ secrets }),
|
||||||
|
key
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import * as stripeController from './stripeController';
|
|
||||||
|
|
||||||
export {
|
|
||||||
stripeController
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import * as stripeController from './stripeController';
|
||||||
|
import * as secretController from './secretController';
|
||||||
|
import * as workspaceController from './workspaceController';
|
||||||
|
|
||||||
|
export {
|
||||||
|
stripeController,
|
||||||
|
secretController,
|
||||||
|
workspaceController
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { SecretVersion } from '../../models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return secret versions for secret with id [secretId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getSecretVersions = async (req: Request, res: Response) => {
|
||||||
|
let secretVersions;
|
||||||
|
try {
|
||||||
|
const { secretId } = req.params;
|
||||||
|
|
||||||
|
const offset: number = parseInt(req.query.offset as string);
|
||||||
|
const limit: number = parseInt(req.query.limit as string);
|
||||||
|
|
||||||
|
secretVersions = await SecretVersion.find({
|
||||||
|
secret: secretId
|
||||||
|
})
|
||||||
|
.skip(offset)
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get secret versions'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secretVersions
|
||||||
|
});
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../config';
|
import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../../../config';
|
||||||
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-08-01'
|
apiVersion: '2022-08-01'
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { SecretSnapshot } from '../../models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return secret snapshots for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => {
|
||||||
|
let secretSnapshots;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
const offset: number = parseInt(req.query.offset as string);
|
||||||
|
const limit: number = parseInt(req.query.limit as string);
|
||||||
|
|
||||||
|
secretSnapshots = await SecretSnapshot.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
})
|
||||||
|
.skip(offset)
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get secret snapshots'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secretSnapshots
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.licenseKey - Infisical license key
|
|
||||||
*/
|
|
||||||
const checkLicenseKey = ({
|
|
||||||
licenseKey
|
|
||||||
}: {
|
|
||||||
licenseKey: string
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
checkLicenseKey
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import {
|
||||||
|
Secret
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
SecretSnapshot,
|
||||||
|
SecretVersion,
|
||||||
|
ISecretVersion
|
||||||
|
} from '../models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a copy of the current state of secrets in workspace with id
|
||||||
|
* [workspaceId] under a new snapshot with incremented version under the
|
||||||
|
* secretsnapshots collection.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.workspaceId
|
||||||
|
*/
|
||||||
|
const takeSecretSnapshotHelper = async ({
|
||||||
|
workspaceId
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const secrets = await Secret.find({
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestSecretSnapshot = await SecretSnapshot.findOne({
|
||||||
|
workspace: workspaceId
|
||||||
|
}).sort({ version: -1 });
|
||||||
|
|
||||||
|
if (!latestSecretSnapshot) {
|
||||||
|
// case: no snapshots exist for workspace -> create first snapshot
|
||||||
|
await new SecretSnapshot({
|
||||||
|
workspace: workspaceId,
|
||||||
|
version: 1,
|
||||||
|
secrets
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// case: snapshots exist for workspace
|
||||||
|
await new SecretSnapshot({
|
||||||
|
workspace: workspaceId,
|
||||||
|
version: latestSecretSnapshot.version + 1,
|
||||||
|
secrets
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to take a secret snapshot');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addSecretVersionsHelper = async ({
|
||||||
|
secretVersions
|
||||||
|
}: {
|
||||||
|
secretVersions: ISecretVersion[]
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await SecretVersion.insertMany(secretVersions);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to add secret versions');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
takeSecretSnapshotHelper,
|
||||||
|
addSecretVersionsHelper
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import SecretSnapshot, { ISecretSnapshot } from "./secretSnapshot";
|
||||||
|
import SecretVersion, { ISecretVersion } from "./secretVersion";
|
||||||
|
|
||||||
|
export {
|
||||||
|
SecretSnapshot,
|
||||||
|
ISecretSnapshot,
|
||||||
|
SecretVersion,
|
||||||
|
ISecretVersion
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { Schema, model, Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
SECRET_SHARED,
|
||||||
|
SECRET_PERSONAL,
|
||||||
|
ENV_DEV,
|
||||||
|
ENV_TESTING,
|
||||||
|
ENV_STAGING,
|
||||||
|
ENV_PROD
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
|
export interface ISecretSnapshot {
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
version: number;
|
||||||
|
secrets: {
|
||||||
|
version: number;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
type: string;
|
||||||
|
user: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretKeyHash: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretValueHash: string;
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretSnapshotSchema = new Schema<ISecretSnapshot>(
|
||||||
|
{
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secrets: [{
|
||||||
|
version: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: [SECRET_SHARED, SECRET_PERSONAL],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
// user associated with the personal secret
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User'
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyCiphertext: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyIV: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyTag: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyHash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueCiphertext: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueIV: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueTag: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueHash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const SecretSnapshot = model<ISecretSnapshot>('SecretSnapshot', secretSnapshotSchema);
|
||||||
|
|
||||||
|
export default SecretSnapshot;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { Schema, model, Types } from 'mongoose';
|
||||||
|
|
||||||
|
export interface ISecretVersion {
|
||||||
|
_id?: Types.ObjectId;
|
||||||
|
secret: Types.ObjectId;
|
||||||
|
version: number;
|
||||||
|
isDeleted: boolean;
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretKeyHash: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretValueHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretVersionSchema = new Schema<ISecretVersion>(
|
||||||
|
{
|
||||||
|
secret: { // could be deleted
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Secret',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isDeleted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyCiphertext: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyIV: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyTag: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretKeyHash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueCiphertext: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueIV: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueTag: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
secretValueHash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const SecretVersion = model<ISecretVersion>('SecretVersion', secretVersionSchema);
|
||||||
|
|
||||||
|
export default SecretVersion;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import secret from './secret';
|
||||||
|
import workspace from './workspace';
|
||||||
|
|
||||||
|
export {
|
||||||
|
secret,
|
||||||
|
workspace
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../../middleware';
|
||||||
|
import { body, query, param } from 'express-validator';
|
||||||
|
import { secretController } from '../../controllers/v1';
|
||||||
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../../variables';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:secretId/secret-versions',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('secretId').exists().trim(),
|
||||||
|
query('offset').exists().isInt(),
|
||||||
|
query('limit').exists().isInt(),
|
||||||
|
validateRequest,
|
||||||
|
secretController.getSecretVersions
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { stripeController } from '../controllers';
|
import { stripeController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post('/webhook', stripeController.handleWebhook);
|
router.post('/webhook', stripeController.handleWebhook);
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../../middleware';
|
||||||
|
import { param, query } from 'express-validator';
|
||||||
|
import { ADMIN, MEMBER, GRANTED } from '../../../variables';
|
||||||
|
import { workspaceController } from '../../controllers/v1';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/secret-snapshots',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
query('offset').exists().isInt(),
|
||||||
|
query('limit').exists().isInt(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceSecretSnapshots
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { LICENSE_KEY } from '../../config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to handle Enterprise Edition license actions
|
||||||
|
*/
|
||||||
|
class EELicenseService {
|
||||||
|
|
||||||
|
private readonly _isLicenseValid: boolean;
|
||||||
|
|
||||||
|
constructor(licenseKey: string) {
|
||||||
|
this._isLicenseValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isLicenseValid(): boolean {
|
||||||
|
return this._isLicenseValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new EELicenseService(LICENSE_KEY);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { ISecretVersion } from '../models';
|
||||||
|
import {
|
||||||
|
takeSecretSnapshotHelper,
|
||||||
|
addSecretVersionsHelper
|
||||||
|
} from '../helpers/secret';
|
||||||
|
import EELicenseService from './EELicenseService';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to handle Enterprise Edition secret actions
|
||||||
|
*/
|
||||||
|
class EESecretService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a copy of the current state of secrets in workspace with id
|
||||||
|
* [workspaceId] under a new snapshot with incremented version under the
|
||||||
|
* SecretSnapshot collection.
|
||||||
|
* Requires a valid license key [licenseKey]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.workspaceId
|
||||||
|
*/
|
||||||
|
static async takeSecretSnapshot({
|
||||||
|
workspaceId
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}) {
|
||||||
|
if (!EELicenseService.isLicenseValid) return;
|
||||||
|
await takeSecretSnapshotHelper({ workspaceId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds secret versions [secretVersions] to the SecretVersion collection.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {SecretVersion} obj.secretVersions
|
||||||
|
*/
|
||||||
|
static async addSecretVersions({
|
||||||
|
secretVersions
|
||||||
|
}: {
|
||||||
|
secretVersions: ISecretVersion[];
|
||||||
|
}) {
|
||||||
|
if (!EELicenseService.isLicenseValid) return;
|
||||||
|
await addSecretVersionsHelper({
|
||||||
|
secretVersions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EESecretService;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import EELicenseService from "./EELicenseService";
|
||||||
|
import EESecretService from "./EESecretService";
|
||||||
|
|
||||||
|
export {
|
||||||
|
EELicenseService,
|
||||||
|
EESecretService
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
decryptAsymmetric
|
decryptAsymmetric
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { decryptSecrets } from '../helpers/secret';
|
|
||||||
import { ENCRYPTION_KEY } from '../config';
|
import { ENCRYPTION_KEY } from '../config';
|
||||||
import { SECRET_SHARED } from '../variables';
|
import { SECRET_SHARED } from '../variables';
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import * as Sentry from '@sentry/node';
|
|||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
Integration,
|
Integration,
|
||||||
IIntegration,
|
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
IIntegrationAuth
|
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||||
import { BotService, IntegrationService } from '../services';
|
import { BotService } from '../services';
|
||||||
import {
|
import {
|
||||||
ENV_DEV,
|
ENV_DEV,
|
||||||
EVENT_PUSH_SECRETS,
|
EVENT_PUSH_SECRETS,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
import RequestError from '../utils/requestError';
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@@ -176,12 +176,13 @@ const syncIntegrationsHelper = async ({
|
|||||||
*/
|
*/
|
||||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
||||||
let refreshToken;
|
let refreshToken;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const integrationAuth = await IntegrationAuth
|
const integrationAuth = await IntegrationAuth
|
||||||
.findById(integrationAuthId)
|
.findById(integrationAuthId)
|
||||||
.select('+refreshCiphertext +refreshIV +refreshTag');
|
.select('+refreshCiphertext +refreshIV +refreshTag');
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||||
|
|
||||||
refreshToken = await BotService.decryptSymmetric({
|
refreshToken = await BotService.decryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace.toString(),
|
workspaceId: integrationAuth.workspace.toString(),
|
||||||
@@ -193,7 +194,10 @@ const syncIntegrationsHelper = async ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to get integration refresh token');
|
if(err instanceof RequestError)
|
||||||
|
throw err
|
||||||
|
else
|
||||||
|
throw new Error('Failed to get integration refresh token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return refreshToken;
|
return refreshToken;
|
||||||
@@ -209,12 +213,13 @@ const syncIntegrationsHelper = async ({
|
|||||||
*/
|
*/
|
||||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
||||||
let accessToken;
|
let accessToken;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const integrationAuth = await IntegrationAuth
|
const integrationAuth = await IntegrationAuth
|
||||||
.findById(integrationAuthId)
|
.findById(integrationAuthId)
|
||||||
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext');
|
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext');
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||||
|
|
||||||
accessToken = await BotService.decryptSymmetric({
|
accessToken = await BotService.decryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace.toString(),
|
workspaceId: integrationAuth.workspace.toString(),
|
||||||
@@ -240,7 +245,10 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to get integration access token');
|
if(err instanceof RequestError)
|
||||||
|
throw err
|
||||||
|
else
|
||||||
|
throw new Error('Failed to get integration access token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessToken;
|
return accessToken;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const validateMembership = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let membership;
|
let membership;
|
||||||
|
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
||||||
try {
|
try {
|
||||||
membership = await Membership.findOne({
|
membership = await Membership.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
|
|||||||
@@ -2,40 +2,10 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import handlebars from 'handlebars';
|
import handlebars from 'handlebars';
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import {
|
import { SMTP_FROM_NAME, SMTP_FROM_ADDRESS } from '../config';
|
||||||
SMTP_HOST,
|
|
||||||
SMTP_PORT,
|
|
||||||
SMTP_NAME,
|
|
||||||
SMTP_USERNAME,
|
|
||||||
SMTP_PASSWORD
|
|
||||||
} from '../config';
|
|
||||||
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
const mailOpts: SMTPConnection.Options = {
|
let smtpTransporter: nodemailer.Transporter;
|
||||||
host: SMTP_HOST,
|
|
||||||
port: SMTP_PORT as number
|
|
||||||
};
|
|
||||||
if (SMTP_USERNAME && SMTP_PASSWORD) {
|
|
||||||
mailOpts.auth = {
|
|
||||||
user: SMTP_USERNAME,
|
|
||||||
pass: SMTP_PASSWORD
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// create nodemailer transporter
|
|
||||||
const transporter = nodemailer.createTransport(mailOpts);
|
|
||||||
transporter
|
|
||||||
.verify()
|
|
||||||
.then(() => {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureMessage('SMTP - Successfully connected');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(
|
|
||||||
`SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -63,8 +33,8 @@ const sendMail = async ({
|
|||||||
const temp = handlebars.compile(html);
|
const temp = handlebars.compile(html);
|
||||||
const htmlToSend = temp(substitutions);
|
const htmlToSend = temp(substitutions);
|
||||||
|
|
||||||
await transporter.sendMail({
|
await smtpTransporter.sendMail({
|
||||||
from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`,
|
from: `"${SMTP_FROM_NAME}" <${SMTP_FROM_ADDRESS}>`,
|
||||||
to: recipients.join(', '),
|
to: recipients.join(', '),
|
||||||
subject: subjectLine,
|
subject: subjectLine,
|
||||||
html: htmlToSend
|
html: htmlToSend
|
||||||
@@ -75,4 +45,8 @@ const sendMail = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { sendMail };
|
const setTransporter = (transporter: nodemailer.Transporter) => {
|
||||||
|
smtpTransporter = transporter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { sendMail, setTransporter };
|
||||||
|
|||||||
+417
-104
@@ -1,12 +1,21 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import {
|
import {
|
||||||
Secret,
|
Secret,
|
||||||
ISecret
|
ISecret,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
|
import {
|
||||||
|
EESecretService
|
||||||
|
} from '../ee/services';
|
||||||
|
import {
|
||||||
|
SecretVersion
|
||||||
|
} from '../ee/models';
|
||||||
|
import {
|
||||||
|
takeSecretSnapshotHelper
|
||||||
|
} from '../ee/helpers/secret';
|
||||||
import { decryptSymmetric } from '../utils/crypto';
|
import { decryptSymmetric } from '../utils/crypto';
|
||||||
import { SECRET_SHARED, SECRET_PERSONAL } from '../variables';
|
import { SECRET_SHARED, SECRET_PERSONAL } from '../variables';
|
||||||
|
|
||||||
interface PushSecret {
|
interface V1PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
ivKey: string;
|
ivKey: string;
|
||||||
tagKey: string;
|
tagKey: string;
|
||||||
@@ -15,11 +24,31 @@ interface PushSecret {
|
|||||||
ivValue: string;
|
ivValue: string;
|
||||||
tagValue: string;
|
tagValue: string;
|
||||||
hashValue: string;
|
hashValue: string;
|
||||||
|
ciphertextComment: string;
|
||||||
|
ivComment: string;
|
||||||
|
tagComment: string;
|
||||||
|
hashComment: string;
|
||||||
type: 'shared' | 'personal';
|
type: 'shared' | 'personal';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface V2PushSecret {
|
||||||
|
type: string; // personal or shared
|
||||||
|
secretKeyCiphertext: string;
|
||||||
|
secretKeyIV: string;
|
||||||
|
secretKeyTag: string;
|
||||||
|
secretKeyHash: string;
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
secretValueHash: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
secretCommentHash?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
[index: string]: string;
|
[index: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecryptSecretType = 'text' | 'object' | 'expanded';
|
type DecryptSecretType = 'text' | 'object' | 'expanded';
|
||||||
@@ -35,7 +64,7 @@ type DecryptSecretType = 'text' | 'object' | 'expanded';
|
|||||||
* @param {String} obj.environment - environment for secrets
|
* @param {String} obj.environment - environment for secrets
|
||||||
* @param {Object[]} obj.secrets - secrets to push
|
* @param {Object[]} obj.secrets - secrets to push
|
||||||
*/
|
*/
|
||||||
const pushSecrets = async ({
|
const v1PushSecrets = async ({
|
||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -44,8 +73,9 @@ const pushSecrets = async ({
|
|||||||
userId: string;
|
userId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secrets: PushSecret[];
|
secrets: V1PushSecret[];
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
|
// TODO: clean up function and fix up types
|
||||||
try {
|
try {
|
||||||
// construct useful data structures
|
// construct useful data structures
|
||||||
const oldSecrets = await pullSecrets({
|
const oldSecrets = await pullSecrets({
|
||||||
@@ -53,74 +83,133 @@ const pushSecrets = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) => {
|
|
||||||
return { ...accumulator, [s.secretKeyHash]: s };
|
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
|
||||||
}, {});
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
const newSecretsObj = secrets.reduce((accumulator, s) => {
|
, {});
|
||||||
return { ...accumulator, [s.hashKey]: s };
|
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
|
||||||
}, {});
|
({ ...accumulator, [`${s.type}-${s.hashKey}`]: s })
|
||||||
|
, {});
|
||||||
|
|
||||||
// handle deleting secrets
|
// handle deleting secrets
|
||||||
const toDelete = oldSecrets.filter(
|
const toDelete = oldSecrets
|
||||||
(s: ISecret) => !(s.secretKeyHash in newSecretsObj)
|
.filter(
|
||||||
);
|
(s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj)
|
||||||
|
)
|
||||||
|
.map((s) => s._id);
|
||||||
if (toDelete.length > 0) {
|
if (toDelete.length > 0) {
|
||||||
await Secret.deleteMany({
|
await Secret.deleteMany({
|
||||||
_id: { $in: toDelete.map((s) => s._id) }
|
_id: { $in: toDelete }
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretVersion.updateMany({
|
||||||
|
secret: { $in: toDelete }
|
||||||
|
}, {
|
||||||
|
isDeleted: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle modifying secrets where type or value changed
|
const toUpdate = oldSecrets
|
||||||
const operations = secrets
|
|
||||||
.filter((s) => {
|
.filter((s) => {
|
||||||
if (s.hashKey in oldSecretsObj) {
|
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||||
if (s.hashValue !== oldSecretsObj[s.hashKey].secretValueHash) {
|
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue
|
||||||
// case: filter secrets where value changed
|
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) {
|
||||||
|
// case: filter secrets where value or comment changed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s.type !== oldSecretsObj[s.hashKey].type) {
|
if (!s.version) {
|
||||||
// case: filter secrets where type changed
|
// case: filter (legacy) secrets that were not versioned
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const operations = toUpdate
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
|
const {
|
||||||
|
ciphertextValue,
|
||||||
|
ivValue,
|
||||||
|
tagValue,
|
||||||
|
hashValue,
|
||||||
|
ciphertextComment,
|
||||||
|
ivComment,
|
||||||
|
tagComment,
|
||||||
|
hashComment
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
const update: Update = {
|
const update: Update = {
|
||||||
type: s.type,
|
secretValueCiphertext: ciphertextValue,
|
||||||
secretValueCiphertext: s.ciphertextValue,
|
secretValueIV: ivValue,
|
||||||
secretValueIV: s.ivValue,
|
secretValueTag: tagValue,
|
||||||
secretValueTag: s.tagValue,
|
secretValueHash: hashValue,
|
||||||
secretValueHash: s.hashValue
|
secretCommentCiphertext: ciphertextComment,
|
||||||
};
|
secretCommentIV: ivComment,
|
||||||
|
secretCommentTag: tagComment,
|
||||||
|
secretCommentHash: hashComment,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: (legacy) secret was not versioned
|
||||||
|
update.version = 1;
|
||||||
|
} else {
|
||||||
|
update['$inc'] = {
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (s.type === SECRET_PERSONAL) {
|
if (s.type === SECRET_PERSONAL) {
|
||||||
// attach user assocaited with the personal secret
|
// attach user associated with the personal secret
|
||||||
update['user'] = userId;
|
update['user'] = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateOne: {
|
updateOne: {
|
||||||
filter: {
|
filter: {
|
||||||
workspace: workspaceId,
|
_id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id
|
||||||
_id: oldSecretsObj[s.hashKey]._id
|
|
||||||
},
|
},
|
||||||
update
|
update
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const a = await Secret.bulkWrite(operations as any);
|
await Secret.bulkWrite(operations as any);
|
||||||
|
|
||||||
|
// (EE) add secret versions for updated secrets
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: toUpdate.map(({
|
||||||
|
_id,
|
||||||
|
version,
|
||||||
|
type,
|
||||||
|
secretKeyHash,
|
||||||
|
}) => {
|
||||||
|
const newSecret = newSecretsObj[`${type}-${secretKeyHash}`];
|
||||||
|
return ({
|
||||||
|
secret: _id,
|
||||||
|
version: version ? version + 1 : 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext: newSecret.ciphertextKey,
|
||||||
|
secretKeyIV: newSecret.ivKey,
|
||||||
|
secretKeyTag: newSecret.tagKey,
|
||||||
|
secretKeyHash: newSecret.hashKey,
|
||||||
|
secretValueCiphertext: newSecret.ciphertextValue,
|
||||||
|
secretValueIV: newSecret.ivValue,
|
||||||
|
secretValueTag: newSecret.tagValue,
|
||||||
|
secretValueHash: newSecret.hashValue
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
// handle adding new secrets
|
// handle adding new secrets
|
||||||
const toAdd = secrets.filter((s) => !(s.hashKey in oldSecretsObj));
|
const toAdd = secrets.filter((s) => !(`${s.type}-${s.hashKey}` in oldSecretsObj));
|
||||||
|
|
||||||
if (toAdd.length > 0) {
|
if (toAdd.length > 0) {
|
||||||
// add secrets
|
// add secrets
|
||||||
await Secret.insertMany(
|
const newSecrets = await Secret.insertMany(
|
||||||
toAdd.map((s, idx) => {
|
toAdd.map((s, idx) => {
|
||||||
let obj: any = {
|
const obj: any = {
|
||||||
|
version: 1,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
type: toAdd[idx].type,
|
type: toAdd[idx].type,
|
||||||
environment,
|
environment,
|
||||||
@@ -131,7 +220,11 @@ const pushSecrets = async ({
|
|||||||
secretValueCiphertext: s.ciphertextValue,
|
secretValueCiphertext: s.ciphertextValue,
|
||||||
secretValueIV: s.ivValue,
|
secretValueIV: s.ivValue,
|
||||||
secretValueTag: s.tagValue,
|
secretValueTag: s.tagValue,
|
||||||
secretValueHash: s.hashValue
|
secretValueHash: s.hashValue,
|
||||||
|
secretCommentCiphertext: s.ciphertextComment,
|
||||||
|
secretCommentIV: s.ivComment,
|
||||||
|
secretCommentTag: s.tagComment,
|
||||||
|
secretCommentHash: s.hashComment
|
||||||
};
|
};
|
||||||
|
|
||||||
if (toAdd[idx].type === 'personal') {
|
if (toAdd[idx].type === 'personal') {
|
||||||
@@ -141,7 +234,282 @@ const pushSecrets = async ({
|
|||||||
return obj;
|
return obj;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// (EE) add secret versions for new secrets
|
||||||
|
EESecretService.addSecretVersions({
|
||||||
|
secretVersions: newSecrets.map(({
|
||||||
|
_id,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}) => ({
|
||||||
|
secret: _id,
|
||||||
|
version: 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to push shared and personal secrets');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push secrets for user with id [userId] to workspace
|
||||||
|
* with id [workspaceId] with environment [environment]. Follow steps:
|
||||||
|
* 1. Handle shared secrets (insert, delete)
|
||||||
|
* 2. handle personal secrets (insert, delete)
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.userId - id of user to push secrets for
|
||||||
|
* @param {String} obj.workspaceId - id of workspace to push to
|
||||||
|
* @param {String} obj.environment - environment for secrets
|
||||||
|
* @param {Object[]} obj.secrets - secrets to push
|
||||||
|
*/
|
||||||
|
const v2PushSecrets = async ({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secrets
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
secrets: V2PushSecret[];
|
||||||
|
}): Promise<void> => {
|
||||||
|
// TODO: clean up function and fix up types
|
||||||
|
try {
|
||||||
|
// construct useful data structures
|
||||||
|
const oldSecrets = await pullSecrets({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
|
||||||
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
|
, {});
|
||||||
|
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
|
||||||
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
|
, {});
|
||||||
|
|
||||||
|
// handle deleting secrets
|
||||||
|
const toDelete = oldSecrets
|
||||||
|
.filter(
|
||||||
|
(s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj)
|
||||||
|
)
|
||||||
|
.map((s) => s._id);
|
||||||
|
if (toDelete.length > 0) {
|
||||||
|
await Secret.deleteMany({
|
||||||
|
_id: { $in: toDelete }
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretVersion.updateMany({
|
||||||
|
secret: { $in: toDelete }
|
||||||
|
}, {
|
||||||
|
isDeleted: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const toUpdate = oldSecrets
|
||||||
|
.filter((s) => {
|
||||||
|
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||||
|
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash
|
||||||
|
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash) {
|
||||||
|
// case: filter secrets where value or comment changed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: filter (legacy) secrets that were not versioned
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const operations = toUpdate
|
||||||
|
.map((s) => {
|
||||||
|
const {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
|
const update: Update = {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: (legacy) secret was not versioned
|
||||||
|
update.version = 1;
|
||||||
|
} else {
|
||||||
|
update['$inc'] = {
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.type === SECRET_PERSONAL) {
|
||||||
|
// attach user associated with the personal secret
|
||||||
|
update['user'] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
_id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id
|
||||||
|
},
|
||||||
|
update
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await Secret.bulkWrite(operations as any);
|
||||||
|
|
||||||
|
// (EE) add secret versions for updated secrets
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: toUpdate.map((s) => {
|
||||||
|
const {
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
|
return ({
|
||||||
|
secret: s._id,
|
||||||
|
version: s.version ? s.version + 1 : 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle adding new secrets
|
||||||
|
const toAdd = secrets.filter((s) => !(`${s.type}-${s.secretKeyHash}` in oldSecretsObj));
|
||||||
|
|
||||||
|
if (toAdd.length > 0) {
|
||||||
|
// add secrets
|
||||||
|
const newSecrets = await Secret.insertMany(
|
||||||
|
toAdd.map(({
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
}, idx) => {
|
||||||
|
const obj: any = {
|
||||||
|
version: 1,
|
||||||
|
workspace: workspaceId,
|
||||||
|
type: toAdd[idx].type,
|
||||||
|
environment,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toAdd[idx].type === 'personal') {
|
||||||
|
obj['user' as keyof typeof obj] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// (EE) add secret versions for new secrets
|
||||||
|
EESecretService.addSecretVersions({
|
||||||
|
secretVersions: newSecrets.map(({
|
||||||
|
_id,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}) => ({
|
||||||
|
secret: _id,
|
||||||
|
version: 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
@@ -222,6 +590,13 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
iv: s.secretValueIV,
|
iv: s.secretValueIV,
|
||||||
tag: s.secretValueTag,
|
tag: s.secretValueTag,
|
||||||
hash: s.secretValueHash
|
hash: s.secretValueHash
|
||||||
|
},
|
||||||
|
secretComment: {
|
||||||
|
workspace: s.workspace,
|
||||||
|
ciphertext: s.secretCommentCiphertext,
|
||||||
|
iv: s.secretCommentIV,
|
||||||
|
tag: s.secretCommentTag,
|
||||||
|
hash: s.secretCommentHash
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -233,71 +608,9 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
return reformatedSecrets;
|
return reformatedSecrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return decrypted secrets in format [format]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object[]} obj.secrets - array of (encrypted) secret key-value pair objects
|
|
||||||
* @param {String} obj.key - symmetric key to decrypt secret key-value pairs
|
|
||||||
* @param {String} obj.format - desired return format that is either "text," "object," or "expanded"
|
|
||||||
* @return {String|Object} (decrypted) secrets also called the content
|
|
||||||
*/
|
|
||||||
const decryptSecrets = ({
|
|
||||||
secrets,
|
|
||||||
key,
|
|
||||||
format
|
|
||||||
}: {
|
|
||||||
secrets: PushSecret[];
|
|
||||||
key: string;
|
|
||||||
format: DecryptSecretType;
|
|
||||||
}) => {
|
|
||||||
// init content
|
|
||||||
let content: any = format === 'text' ? '' : {};
|
|
||||||
|
|
||||||
// decrypt secrets
|
|
||||||
secrets.forEach((s, idx) => {
|
|
||||||
const secretKey = decryptSymmetric({
|
|
||||||
ciphertext: s.ciphertextKey,
|
|
||||||
iv: s.ivKey,
|
|
||||||
tag: s.tagKey,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretValue = decryptSymmetric({
|
|
||||||
ciphertext: s.ciphertextValue,
|
|
||||||
iv: s.ivValue,
|
|
||||||
tag: s.tagValue,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 'text':
|
|
||||||
content += secretKey;
|
|
||||||
content += '=';
|
|
||||||
content += secretValue;
|
|
||||||
|
|
||||||
if (idx < secrets.length) {
|
|
||||||
content += '\n';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
content[secretKey] = secretValue;
|
|
||||||
break;
|
|
||||||
case 'expanded':
|
|
||||||
content[secretKey] = {
|
|
||||||
...s,
|
|
||||||
plaintextKey: secretKey,
|
|
||||||
plaintextValue: secretValue
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return content;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
pushSecrets,
|
v1PushSecrets,
|
||||||
|
v2PushSecrets,
|
||||||
pullSecrets,
|
pullSecrets,
|
||||||
reformatPullSecrets,
|
reformatPullSecrets
|
||||||
decryptSecrets
|
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-123
@@ -1,131 +1,25 @@
|
|||||||
/* eslint-disable no-console */
|
|
||||||
import http from 'http';
|
|
||||||
import express from 'express';
|
|
||||||
import helmet from 'helmet';
|
|
||||||
import cors from 'cors';
|
|
||||||
import cookieParser from 'cookie-parser';
|
|
||||||
import mongoose from 'mongoose';
|
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL } from './config';
|
import { SENTRY_DSN, NODE_ENV, MONGO_URL } from './config';
|
||||||
import { apiLimiter } from './helpers/rateLimiter';
|
import { server } from './app';
|
||||||
import { createTerminus } from '@godaddy/terminus';
|
import { initDatabase } from './services/database';
|
||||||
|
import { setUpHealthEndpoint } from './services/health';
|
||||||
|
import { initSmtp } from './services/smtp';
|
||||||
|
import { setTransporter } from './helpers/nodemailer';
|
||||||
|
|
||||||
const app = express();
|
initDatabase(MONGO_URL);
|
||||||
|
|
||||||
Sentry.init({
|
setUpHealthEndpoint(server);
|
||||||
dsn: SENTRY_DSN,
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
debug: NODE_ENV === 'production' ? false : true,
|
|
||||||
environment: NODE_ENV
|
|
||||||
});
|
|
||||||
|
|
||||||
import {
|
setTransporter(initSmtp());
|
||||||
signup as signupRouter,
|
|
||||||
auth as authRouter,
|
|
||||||
bot as botRouter,
|
|
||||||
organization as organizationRouter,
|
|
||||||
workspace as workspaceRouter,
|
|
||||||
membershipOrg as membershipOrgRouter,
|
|
||||||
membership as membershipRouter,
|
|
||||||
key as keyRouter,
|
|
||||||
inviteOrg as inviteOrgRouter,
|
|
||||||
user as userRouter,
|
|
||||||
userAction as userActionRouter,
|
|
||||||
secret as secretRouter,
|
|
||||||
serviceToken as serviceTokenRouter,
|
|
||||||
password as passwordRouter,
|
|
||||||
stripe as stripeRouter,
|
|
||||||
integration as integrationRouter,
|
|
||||||
integrationAuth as integrationAuthRouter,
|
|
||||||
log as logRouter
|
|
||||||
} from './routes';
|
|
||||||
|
|
||||||
const connectWithRetry = () => {
|
if (NODE_ENV !== 'test') {
|
||||||
mongoose
|
Sentry.init({
|
||||||
.connect(MONGO_URL)
|
dsn: SENTRY_DSN,
|
||||||
.then(() => console.log('Successfully connected to DB'))
|
tracesSampleRate: 1.0,
|
||||||
.catch((e) => {
|
debug: NODE_ENV === 'production' ? false : true,
|
||||||
console.log('Failed to connect to DB ', e);
|
environment: NODE_ENV
|
||||||
setTimeout(() => {
|
});
|
||||||
console.log(e);
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
return mongoose.connection;
|
|
||||||
};
|
|
||||||
|
|
||||||
const dbConnection = connectWithRetry();
|
|
||||||
|
|
||||||
app.enable('trust proxy');
|
|
||||||
app.use(cookieParser());
|
|
||||||
app.use(
|
|
||||||
cors({
|
|
||||||
credentials: true,
|
|
||||||
origin: SITE_URL
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (NODE_ENV === 'production') {
|
|
||||||
// enable app-wide rate-limiting + helmet security
|
|
||||||
// in production
|
|
||||||
app.disable('x-powered-by');
|
|
||||||
app.use(apiLimiter);
|
|
||||||
app.use(helmet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
// routers
|
|
||||||
app.use('/api/v1/signup', signupRouter);
|
|
||||||
app.use('/api/v1/auth', authRouter);
|
|
||||||
app.use('/api/v1/bot', botRouter);
|
|
||||||
app.use('/api/v1/user', userRouter);
|
|
||||||
app.use('/api/v1/user-action', userActionRouter);
|
|
||||||
app.use('/api/v1/organization', organizationRouter);
|
|
||||||
app.use('/api/v1/workspace', workspaceRouter);
|
|
||||||
app.use('/api/v1/membership-org', membershipOrgRouter);
|
|
||||||
app.use('/api/v1/membership', membershipRouter);
|
|
||||||
app.use('/api/v1/key', keyRouter);
|
|
||||||
app.use('/api/v1/invite-org', inviteOrgRouter);
|
|
||||||
app.use('/api/v1/secret', secretRouter);
|
|
||||||
app.use('/api/v1/service-token', serviceTokenRouter);
|
|
||||||
app.use('/api/v1/password', passwordRouter);
|
|
||||||
app.use('/api/v1/stripe', stripeRouter);
|
|
||||||
app.use('/api/v1/integration', integrationRouter);
|
|
||||||
app.use('/api/v1/integration-auth', integrationAuthRouter);
|
|
||||||
app.use('/api/v1/log', logRouter);
|
|
||||||
|
|
||||||
const server = http.createServer(app);
|
|
||||||
|
|
||||||
const onSignal = () => {
|
|
||||||
console.log('Server is starting clean-up');
|
|
||||||
return Promise.all([
|
|
||||||
() => {
|
|
||||||
dbConnection.close(() => {
|
|
||||||
console.info('Database connection closed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const healthCheck = () => {
|
|
||||||
// `state.isShuttingDown` (boolean) shows whether the server is shutting down or not
|
|
||||||
return Promise
|
|
||||||
.resolve
|
|
||||||
// optionally include a resolve value to be included as
|
|
||||||
// info in the health check response
|
|
||||||
();
|
|
||||||
};
|
|
||||||
|
|
||||||
createTerminus(server, {
|
|
||||||
healthChecks: {
|
|
||||||
'/healthcheck': healthCheck,
|
|
||||||
onSignal
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
|
||||||
console.log('Listening on PORT ' + PORT);
|
|
||||||
});
|
|
||||||
|
|||||||
+167
-123
@@ -1,17 +1,22 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Octokit } from '@octokit/rest';
|
||||||
|
import { IIntegrationAuth } from '../models';
|
||||||
import {
|
import {
|
||||||
IIntegrationAuth
|
INTEGRATION_HEROKU,
|
||||||
} from '../models';
|
INTEGRATION_VERCEL,
|
||||||
import {
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_API_URL
|
||||||
INTEGRATION_NETLIFY_API_URL
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
|
interface GitHubApp {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for integration named [integration]
|
* Return list of names of apps for integration named [integration]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -21,47 +26,51 @@ import {
|
|||||||
* @returns {String} apps.name - name of integration app
|
* @returns {String} apps.name - name of integration app
|
||||||
*/
|
*/
|
||||||
const getApps = async ({
|
const getApps = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
interface App {
|
||||||
interface App {
|
name: string;
|
||||||
name: string;
|
siteId?: string;
|
||||||
siteId?: string;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let apps: App[]; // TODO: add type and define payloads for apps
|
let apps: App[]; // TODO: add type and define payloads for apps
|
||||||
try {
|
try {
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
apps = await getAppsHeroku({
|
apps = await getAppsHeroku({
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
apps = await getAppsVercel({
|
apps = await getAppsVercel({
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
apps = await getAppsNetlify({
|
apps = await getAppsNetlify({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
|
apps = await getAppsGithub({
|
||||||
} catch (err) {
|
integrationAuth,
|
||||||
Sentry.setUser(null);
|
accessToken
|
||||||
Sentry.captureException(err);
|
});
|
||||||
throw new Error('Failed to get integration apps');
|
break;
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
return apps;
|
Sentry.setUser(null);
|
||||||
}
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get integration apps');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for Heroku integration
|
* Return list of names of apps for Heroku integration
|
||||||
@@ -70,31 +79,29 @@ const getApps = async ({
|
|||||||
* @returns {Object[]} apps - names of Heroku apps
|
* @returns {Object[]} apps - names of Heroku apps
|
||||||
* @returns {String} apps.name - name of Heroku app
|
* @returns {String} apps.name - name of Heroku app
|
||||||
*/
|
*/
|
||||||
const getAppsHeroku = async ({
|
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
||||||
accessToken
|
let apps;
|
||||||
}: {
|
try {
|
||||||
accessToken: string;
|
const res = (
|
||||||
}) => {
|
await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
|
||||||
let apps;
|
headers: {
|
||||||
try {
|
Accept: 'application/vnd.heroku+json; version=3',
|
||||||
const res = (await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
|
Authorization: `Bearer ${accessToken}`
|
||||||
headers: {
|
}
|
||||||
Accept: 'application/vnd.heroku+json; version=3',
|
})
|
||||||
Authorization: `Bearer ${accessToken}`
|
).data;
|
||||||
}
|
|
||||||
})).data;
|
apps = res.map((a: any) => ({
|
||||||
|
name: a.name
|
||||||
apps = res.map((a: any) => ({
|
}));
|
||||||
name: a.name
|
} catch (err) {
|
||||||
}));
|
Sentry.setUser(null);
|
||||||
} catch (err) {
|
Sentry.captureException(err);
|
||||||
Sentry.setUser(null);
|
throw new Error('Failed to get Heroku integration apps');
|
||||||
Sentry.captureException(err);
|
}
|
||||||
throw new Error('Failed to get Heroku integration apps');
|
|
||||||
}
|
return apps;
|
||||||
|
};
|
||||||
return apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for Vercel integration
|
* Return list of names of apps for Vercel integration
|
||||||
@@ -103,30 +110,28 @@ const getAppsHeroku = async ({
|
|||||||
* @returns {Object[]} apps - names of Vercel apps
|
* @returns {Object[]} apps - names of Vercel apps
|
||||||
* @returns {String} apps.name - name of Vercel app
|
* @returns {String} apps.name - name of Vercel app
|
||||||
*/
|
*/
|
||||||
const getAppsVercel = async ({
|
const getAppsVercel = async ({ accessToken }: { accessToken: string }) => {
|
||||||
accessToken
|
let apps;
|
||||||
}: {
|
try {
|
||||||
accessToken: string;
|
const res = (
|
||||||
}) => {
|
await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
|
||||||
let apps;
|
headers: {
|
||||||
try {
|
Authorization: `Bearer ${accessToken}`
|
||||||
const res = (await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
|
}
|
||||||
headers: {
|
})
|
||||||
Authorization: `Bearer ${accessToken}`
|
).data;
|
||||||
}
|
|
||||||
})).data;
|
apps = res.projects.map((a: any) => ({
|
||||||
|
name: a.name
|
||||||
apps = res.projects.map((a: any) => ({
|
}));
|
||||||
name: a.name
|
} catch (err) {
|
||||||
}));
|
Sentry.setUser(null);
|
||||||
} catch (err) {
|
Sentry.captureException(err);
|
||||||
Sentry.setUser(null);
|
throw new Error('Failed to get Vercel integration apps');
|
||||||
Sentry.captureException(err);
|
}
|
||||||
throw new Error('Failed to get Vercel integration apps');
|
|
||||||
}
|
return apps;
|
||||||
|
};
|
||||||
return apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of sites for Netlify integration
|
* Return list of names of sites for Netlify integration
|
||||||
@@ -136,34 +141,73 @@ const getAppsVercel = async ({
|
|||||||
* @returns {String} apps.name - name of Netlify site
|
* @returns {String} apps.name - name of Netlify site
|
||||||
*/
|
*/
|
||||||
const getAppsNetlify = async ({
|
const getAppsNetlify = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let apps;
|
let apps;
|
||||||
try {
|
try {
|
||||||
const res = (await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
|
const res = (
|
||||||
headers: {
|
await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
|
||||||
Authorization: `Bearer ${accessToken}`
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${accessToken}`
|
||||||
})).data;
|
}
|
||||||
|
})
|
||||||
apps = res.map((a: any) => ({
|
).data;
|
||||||
name: a.name,
|
|
||||||
siteId: a.site_id
|
|
||||||
}));
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to get Netlify integration apps');
|
|
||||||
}
|
|
||||||
|
|
||||||
return apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
apps = res.map((a: any) => ({
|
||||||
getApps
|
name: a.name,
|
||||||
}
|
siteId: a.site_id
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get Netlify integration apps');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of names of repositories for Github integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for Netlify API
|
||||||
|
* @returns {Object[]} apps - names of Netlify sites
|
||||||
|
* @returns {String} apps.name - name of Netlify site
|
||||||
|
*/
|
||||||
|
const getAppsGithub = async ({
|
||||||
|
integrationAuth,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integrationAuth: IIntegrationAuth;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
let apps;
|
||||||
|
try {
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
const repos = (await octokit.request(
|
||||||
|
'GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}',
|
||||||
|
{}
|
||||||
|
)).data;
|
||||||
|
|
||||||
|
apps = repos
|
||||||
|
.filter((a:any) => a.permissions.admin === true)
|
||||||
|
.map((a: any) => ({
|
||||||
|
name: a.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get Github repos');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getApps };
|
||||||
|
|||||||
@@ -1,46 +1,56 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
ACTION_PUSH_TO_HEROKU
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
|
INTEGRATION_GITHUB_API_URL
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
CLIENT_SECRET_HEROKU,
|
CLIENT_ID_VERCEL,
|
||||||
CLIENT_ID_VERCEL,
|
CLIENT_ID_NETLIFY,
|
||||||
CLIENT_ID_NETLIFY,
|
CLIENT_ID_GITHUB,
|
||||||
CLIENT_SECRET_VERCEL,
|
CLIENT_SECRET_HEROKU,
|
||||||
CLIENT_SECRET_NETLIFY
|
CLIENT_SECRET_VERCEL,
|
||||||
|
CLIENT_SECRET_NETLIFY,
|
||||||
|
CLIENT_SECRET_GITHUB
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
interface ExchangeCodeHerokuResponse {
|
interface ExchangeCodeHerokuResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
expires_in: number;
|
expires_in: number;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
session_nonce?: string;
|
session_nonce?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExchangeCodeVercelResponse {
|
interface ExchangeCodeVercelResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
installation_id: string;
|
installation_id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
team_id?: string;
|
team_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExchangeCodeNetlifyResponse {
|
interface ExchangeCodeNetlifyResponse {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
token_type: string;
|
token_type: string;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
scope: string;
|
scope: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExchangeCodeGithubResponse {
|
||||||
|
access_token: string;
|
||||||
|
scope: string;
|
||||||
|
token_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,40 +66,45 @@ interface ExchangeCodeNetlifyResponse {
|
|||||||
* @returns {String} obj.action - integration action for bot sequence
|
* @returns {String} obj.action - integration action for bot sequence
|
||||||
*/
|
*/
|
||||||
const exchangeCode = async ({
|
const exchangeCode = async ({
|
||||||
integration,
|
integration,
|
||||||
code
|
code
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integration: string;
|
||||||
code: string;
|
code: string;
|
||||||
}) => {
|
}) => {
|
||||||
let obj = {} as any;
|
let obj = {} as any;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (integration) {
|
switch (integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
obj = await exchangeCodeHeroku({
|
obj = await exchangeCodeHeroku({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
obj = await exchangeCodeVercel({
|
obj = await exchangeCodeVercel({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
obj = await exchangeCodeNetlify({
|
obj = await exchangeCodeNetlify({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
} catch (err) {
|
obj = await exchangeCodeGithub({
|
||||||
Sentry.setUser(null);
|
code
|
||||||
Sentry.captureException(err);
|
});
|
||||||
throw new Error('Failed OAuth2 code-token exchange');
|
break;
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
return obj;
|
Sentry.setUser(null);
|
||||||
}
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed OAuth2 code-token exchange');
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
|
||||||
@@ -144,35 +159,33 @@ const exchangeCodeHeroku = async ({
|
|||||||
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
||||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
*/
|
*/
|
||||||
const exchangeCodeVercel = async ({
|
const exchangeCodeVercel = async ({ code }: { code: string }) => {
|
||||||
code
|
let res: ExchangeCodeVercelResponse;
|
||||||
}: {
|
try {
|
||||||
code: string;
|
res = (
|
||||||
}) => {
|
await axios.post(
|
||||||
let res: ExchangeCodeVercelResponse;
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
try {
|
new URLSearchParams({
|
||||||
res = (await axios.post(
|
code: code,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
client_id: CLIENT_ID_VERCEL,
|
||||||
new URLSearchParams({
|
client_secret: CLIENT_SECRET_VERCEL,
|
||||||
code: code,
|
redirect_uri: `${SITE_URL}/vercel`
|
||||||
client_id: CLIENT_ID_VERCEL,
|
} as any)
|
||||||
client_secret: CLIENT_SECRET_VERCEL,
|
)
|
||||||
redirect_uri: `${SITE_URL}/vercel`
|
).data;
|
||||||
} as any)
|
} catch (err) {
|
||||||
)).data;
|
Sentry.setUser(null);
|
||||||
} catch (err) {
|
Sentry.captureException(err);
|
||||||
Sentry.setUser(null);
|
throw new Error('Failed OAuth2 code-token exchange with Vercel');
|
||||||
Sentry.captureException(err);
|
}
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Vercel');
|
|
||||||
}
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
return ({
|
refreshToken: null,
|
||||||
accessToken: res.access_token,
|
accessExpiresAt: null,
|
||||||
refreshToken: null,
|
teamId: res.team_id
|
||||||
accessExpiresAt: null,
|
};
|
||||||
teamId: res.team_id
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
|
||||||
@@ -184,58 +197,89 @@ const exchangeCodeVercel = async ({
|
|||||||
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
||||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
*/
|
*/
|
||||||
const exchangeCodeNetlify = async ({
|
const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
||||||
code
|
let res: ExchangeCodeNetlifyResponse;
|
||||||
}: {
|
let accountId;
|
||||||
code: string;
|
try {
|
||||||
}) => {
|
res = (
|
||||||
let res: ExchangeCodeNetlifyResponse;
|
await axios.post(
|
||||||
let accountId;
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
try {
|
new URLSearchParams({
|
||||||
res = (await axios.post(
|
grant_type: 'authorization_code',
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
code: code,
|
||||||
new URLSearchParams({
|
client_id: CLIENT_ID_NETLIFY,
|
||||||
grant_type: 'authorization_code',
|
client_secret: CLIENT_SECRET_NETLIFY,
|
||||||
code: code,
|
redirect_uri: `${SITE_URL}/netlify`
|
||||||
client_id: CLIENT_ID_NETLIFY,
|
} as any)
|
||||||
client_secret: CLIENT_SECRET_NETLIFY,
|
)
|
||||||
redirect_uri: `${SITE_URL}/netlify`
|
).data;
|
||||||
} as any)
|
|
||||||
)).data;
|
|
||||||
|
|
||||||
const res2 = await axios.get(
|
const res2 = await axios.get('https://api.netlify.com/api/v1/sites', {
|
||||||
'https://api.netlify.com/api/v1/sites',
|
headers: {
|
||||||
{
|
Authorization: `Bearer ${res.access_token}`
|
||||||
headers: {
|
}
|
||||||
Authorization: `Bearer ${res.access_token}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const res3 = (await axios.get(
|
|
||||||
'https://api.netlify.com/api/v1/accounts',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${res.access_token}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)).data;
|
|
||||||
|
|
||||||
accountId = res3[0].id;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Netlify');
|
|
||||||
}
|
|
||||||
|
|
||||||
return ({
|
|
||||||
accessToken: res.access_token,
|
|
||||||
refreshToken: res.refresh_token,
|
|
||||||
accountId
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
const res3 = (
|
||||||
exchangeCode
|
await axios.get('https://api.netlify.com/api/v1/accounts', {
|
||||||
}
|
headers: {
|
||||||
|
Authorization: `Bearer ${res.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
accountId = res3[0].id;
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed OAuth2 code-token exchange with Netlify');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
|
refreshToken: res.refresh_token,
|
||||||
|
accountId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Github
|
||||||
|
* code-token exchange
|
||||||
|
* @param {Object} obj1
|
||||||
|
* @param {Object} obj1.code - code for code-token exchange
|
||||||
|
* @returns {Object} obj2
|
||||||
|
* @returns {String} obj2.accessToken - access token for Github API
|
||||||
|
* @returns {String} obj2.refreshToken - refresh token for Github API
|
||||||
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
|
*/
|
||||||
|
const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||||
|
let res: ExchangeCodeGithubResponse;
|
||||||
|
try {
|
||||||
|
res = (
|
||||||
|
await axios.get(INTEGRATION_GITHUB_TOKEN_URL, {
|
||||||
|
params: {
|
||||||
|
client_id: CLIENT_ID_GITHUB,
|
||||||
|
client_secret: CLIENT_SECRET_GITHUB,
|
||||||
|
code: code,
|
||||||
|
redirect_uri: `${SITE_URL}/github`
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed OAuth2 code-token exchange with Github');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
|
refreshToken: null,
|
||||||
|
accessExpiresAt: null
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { exchangeCode };
|
||||||
|
|||||||
@@ -13,46 +13,47 @@ import {
|
|||||||
* named [integration]
|
* named [integration]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.integration - name of integration
|
* @param {String} obj.integration - name of integration
|
||||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
||||||
*/
|
*/
|
||||||
const exchangeRefresh = async ({
|
const exchangeRefresh = async ({
|
||||||
integration,
|
integration,
|
||||||
refreshToken
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integration: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let accessToken;
|
let accessToken;
|
||||||
try {
|
try {
|
||||||
switch (integration) {
|
switch (integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
accessToken = await exchangeRefreshHeroku({
|
accessToken = await exchangeRefreshHeroku({
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to get new OAuth2 access token');
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
return accessToken;
|
Sentry.setUser(null);
|
||||||
}
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get new OAuth2 access token');
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
* Heroku integration
|
* Heroku integration
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const exchangeRefreshHeroku = async ({
|
const exchangeRefreshHeroku = async ({
|
||||||
refreshToken
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let accessToken;
|
let accessToken;
|
||||||
|
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const res = await axios.post(
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
@@ -63,16 +64,14 @@ const exchangeRefreshHeroku = async ({
|
|||||||
} as any)
|
} as any)
|
||||||
);
|
);
|
||||||
|
|
||||||
accessToken = res.data.access_token;
|
accessToken = res.data.access_token;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to get new OAuth2 access token for Heroku');
|
throw new Error('Failed to get new OAuth2 access token for Heroku');
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
return accessToken;
|
||||||
exchangeRefresh
|
};
|
||||||
}
|
|
||||||
|
export { exchangeRefresh };
|
||||||
|
|||||||
@@ -1,50 +1,47 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { IIntegrationAuth, IntegrationAuth, Integration } from '../models';
|
||||||
import {
|
import {
|
||||||
IIntegrationAuth,
|
INTEGRATION_HEROKU,
|
||||||
IntegrationAuth,
|
INTEGRATION_VERCEL,
|
||||||
Integration
|
INTEGRATION_NETLIFY,
|
||||||
} from '../models';
|
INTEGRATION_GITHUB
|
||||||
import {
|
|
||||||
INTEGRATION_HEROKU,
|
|
||||||
INTEGRATION_VERCEL,
|
|
||||||
INTEGRATION_NETLIFY
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
const revokeAccess = async ({
|
const revokeAccess = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth,
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
// add any integration-specific revocation logic
|
// add any integration-specific revocation logic
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
|
break;
|
||||||
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
|
||||||
_id: integrationAuth._id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deletedIntegrationAuth) {
|
|
||||||
await Integration.deleteMany({
|
|
||||||
integrationAuth: deletedIntegrationAuth._id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to delete integration authorization');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
||||||
revokeAccess
|
_id: integrationAuth._id
|
||||||
}
|
});
|
||||||
|
|
||||||
|
if (deletedIntegrationAuth) {
|
||||||
|
await Integration.deleteMany({
|
||||||
|
integrationAuth: deletedIntegrationAuth._id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to delete integration authorization');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { revokeAccess };
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Octokit } from '@octokit/rest';
|
||||||
|
// import * as sodium from 'libsodium-wrappers';
|
||||||
|
import sodium from 'libsodium-wrappers';
|
||||||
|
// const sodium = require('libsodium-wrappers');
|
||||||
|
import { IIntegration, IIntegrationAuth } from '../models';
|
||||||
import {
|
import {
|
||||||
IIntegration, IIntegrationAuth
|
INTEGRATION_HEROKU,
|
||||||
} from '../models';
|
INTEGRATION_VERCEL,
|
||||||
import {
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_API_URL
|
||||||
INTEGRATION_NETLIFY_API_URL
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
import { access, appendFile } from 'fs';
|
||||||
|
|
||||||
// TODO: need a helper function in the future to handle integration
|
// TODO: need a helper function in the future to handle integration
|
||||||
// envar priorities (i.e. prioritize secrets within integration or those on Infisical)
|
// envar priorities (i.e. prioritize secrets within integration or those on Infisical)
|
||||||
@@ -26,49 +31,54 @@ import {
|
|||||||
* @param {String} obj.accessToken - access token for integration
|
* @param {String} obj.accessToken - access token for integration
|
||||||
*/
|
*/
|
||||||
const syncSecrets = async ({
|
const syncSecrets = async ({
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken,
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integration: IIntegration;
|
integration: IIntegration;
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
secrets: any;
|
secrets: any;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
switch (integration.integration) {
|
switch (integration.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
await syncSecretsHeroku({
|
await syncSecretsHeroku({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
await syncSecretsVercel({
|
await syncSecretsVercel({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
await syncSecretsNetlify({
|
await syncSecretsNetlify({
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
|
await syncSecretsGitHub({
|
||||||
// TODO: set integration to inactive if it was not synced correctly (send alert?)
|
integration,
|
||||||
} catch (err) {
|
secrets,
|
||||||
Sentry.setUser(null);
|
accessToken
|
||||||
Sentry.captureException(err);
|
});
|
||||||
throw new Error('Failed to sync secrets to integration');
|
break;
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to integration');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to Heroku [app]
|
* Sync/push [secrets] to Heroku [app]
|
||||||
@@ -77,47 +87,49 @@ const syncSecrets = async ({
|
|||||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
*/
|
*/
|
||||||
const syncSecretsHeroku = async ({
|
const syncSecretsHeroku = async ({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integration: IIntegration,
|
integration: IIntegration;
|
||||||
secrets: any;
|
secrets: any;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const herokuSecrets = (await axios.get(
|
const herokuSecrets = (
|
||||||
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
await axios.get(
|
||||||
{
|
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||||
headers: {
|
{
|
||||||
Accept: 'application/vnd.heroku+json; version=3',
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`
|
Accept: 'application/vnd.heroku+json; version=3',
|
||||||
}
|
Authorization: `Bearer ${accessToken}`
|
||||||
}
|
}
|
||||||
)).data;
|
}
|
||||||
|
)
|
||||||
Object.keys(herokuSecrets).forEach(key => {
|
).data;
|
||||||
if (!(key in secrets)) {
|
|
||||||
secrets[key] = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await axios.patch(
|
Object.keys(herokuSecrets).forEach((key) => {
|
||||||
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
if (!(key in secrets)) {
|
||||||
secrets,
|
secrets[key] = null;
|
||||||
{
|
}
|
||||||
headers: {
|
});
|
||||||
Accept: 'application/vnd.heroku+json; version=3',
|
|
||||||
Authorization: `Bearer ${accessToken}`
|
await axios.patch(
|
||||||
}
|
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||||
}
|
secrets,
|
||||||
);
|
{
|
||||||
} catch (err) {
|
headers: {
|
||||||
Sentry.setUser(null);
|
Accept: 'application/vnd.heroku+json; version=3',
|
||||||
Sentry.captureException(err);
|
Authorization: `Bearer ${accessToken}`
|
||||||
throw new Error('Failed to sync secrets to Heroku');
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to Heroku');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to Heroku [app]
|
* Sync/push [secrets] to Heroku [app]
|
||||||
@@ -474,8 +486,120 @@ const syncSecretsNetlify = async ({
|
|||||||
throw new Error('Failed to sync secrets to Heroku');
|
throw new Error('Failed to sync secrets to Heroku');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export {
|
/**
|
||||||
syncSecrets
|
* Sync/push [secrets] to GitHub [repo]
|
||||||
}
|
* @param {Object} obj
|
||||||
|
* @param {IIntegration} obj.integration - integration details
|
||||||
|
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
|
||||||
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
|
*/
|
||||||
|
const syncSecretsGitHub = async ({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integration: IIntegration;
|
||||||
|
secrets: any;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
interface GitHubRepoKey {
|
||||||
|
key_id: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubSecret {
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubSecretRes {
|
||||||
|
[index: string]: GitHubSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSecrets: GitHubSecret[] = [];
|
||||||
|
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = (await octokit.request('GET /user', {})).data;
|
||||||
|
|
||||||
|
const repoPublicKey: GitHubRepoKey = (await octokit.request(
|
||||||
|
'GET /repos/{owner}/{repo}/actions/secrets/public-key',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app
|
||||||
|
}
|
||||||
|
)).data;
|
||||||
|
|
||||||
|
// // Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key
|
||||||
|
const encryptedSecrets: GitHubSecretRes = (await octokit.request(
|
||||||
|
'GET /repos/{owner}/{repo}/actions/secrets',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.data
|
||||||
|
.secrets
|
||||||
|
.reduce((obj: any, secret: any) => ({
|
||||||
|
...obj,
|
||||||
|
[secret.name]: secret
|
||||||
|
}), {});
|
||||||
|
|
||||||
|
Object.keys(encryptedSecrets).map(async (key) => {
|
||||||
|
if (!(key in secrets)) {
|
||||||
|
await octokit.request(
|
||||||
|
'DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app,
|
||||||
|
secret_name: key
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(secrets).map((key) => {
|
||||||
|
// let encryptedSecret;
|
||||||
|
sodium.ready.then(async () => {
|
||||||
|
// convert secret & base64 key to Uint8Array.
|
||||||
|
const binkey = sodium.from_base64(
|
||||||
|
repoPublicKey.key,
|
||||||
|
sodium.base64_variants.ORIGINAL
|
||||||
|
);
|
||||||
|
const binsec = sodium.from_string(secrets[key]);
|
||||||
|
|
||||||
|
// encrypt secret using libsodium
|
||||||
|
const encBytes = sodium.crypto_box_seal(binsec, binkey);
|
||||||
|
|
||||||
|
// convert encrypted Uint8Array to base64
|
||||||
|
const encryptedSecret = sodium.to_base64(
|
||||||
|
encBytes,
|
||||||
|
sodium.base64_variants.ORIGINAL
|
||||||
|
);
|
||||||
|
|
||||||
|
await octokit.request(
|
||||||
|
'PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app,
|
||||||
|
secret_name: key,
|
||||||
|
encrypted_value: encryptedSecret,
|
||||||
|
key_id: repoPublicKey.key_id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to GitHub');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { syncSecrets };
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ErrorRequestHandler } from "express";
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { InternalServerError } from "../utils/errors";
|
||||||
|
import { getLogger } from "../utils/logger";
|
||||||
|
import RequestError, { LogLevel } from "../utils/requestError";
|
||||||
|
|
||||||
|
|
||||||
|
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError|Error, req, res, next) => {
|
||||||
|
if(res.headersSent) return next();
|
||||||
|
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
|
||||||
|
if(!(error instanceof RequestError)){
|
||||||
|
error = InternalServerError({context: {exception: error.message}, stack: error.stack})
|
||||||
|
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
|
||||||
|
}
|
||||||
|
|
||||||
|
//* Set Sentry user identification if req.user is populated
|
||||||
|
if(req.user !== undefined && req.user !== null){
|
||||||
|
Sentry.setUser({ email: req.user.email })
|
||||||
|
}
|
||||||
|
//* Only sent error to Sentry if LogLevel is one of the following level 'ERROR', 'EMERGENCY' or 'CRITICAL'
|
||||||
|
//* with this we will eliminate false-positive errors like 'BadRequestError', 'UnauthorizedRequestError' and so on
|
||||||
|
if([LogLevel.ERROR, LogLevel.EMERGENCY, LogLevel.CRITICAL].includes((<RequestError>error).level)){
|
||||||
|
Sentry.captureException(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status((<RequestError>error).statusCode).json((<RequestError>error).format(req))
|
||||||
|
next()
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { User } from '../models';
|
import { User } from '../models';
|
||||||
import { JWT_AUTH_SECRET } from '../config';
|
import { JWT_AUTH_SECRET } from '../config';
|
||||||
|
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -20,32 +20,25 @@ declare module 'jsonwebtoken' {
|
|||||||
*/
|
*/
|
||||||
const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
|
const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// JWT authentication middleware
|
// JWT authentication middleware
|
||||||
try {
|
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
|
||||||
if (!req.headers?.authorization)
|
if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
|
||||||
throw new Error('Failed to locate authorization header');
|
if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
|
||||||
|
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||||
|
|
||||||
const token = req.headers.authorization.split(' ')[1];
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
jwt.verify(AUTH_TOKEN_VALUE, JWT_AUTH_SECRET)
|
||||||
jwt.verify(token, JWT_AUTH_SECRET)
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
_id: decodedToken.userId
|
_id: decodedToken.userId
|
||||||
}).select('+publicKey');
|
}).select('+publicKey');
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to authenticate unfound user');
|
if (!user) return next(AccountNotFoundError({message: 'Failed to locate User account'}))
|
||||||
if (!user?.publicKey)
|
if (!user?.publicKey)
|
||||||
throw new Error('Failed to authenticate not fully set up account');
|
return next(UnauthorizedRequestError({message: 'Unable to authenticate due to partially set up account'}))
|
||||||
|
|
||||||
req.user = user;
|
req.user = user;
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed to authenticate user. Try logging in'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default requireAuth;
|
export default requireAuth;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Bot } from '../models';
|
import { Bot } from '../models';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { AccountNotFoundError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -15,30 +15,22 @@ const requireBotAuth = ({
|
|||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const bot = await Bot.findOne({ _id: req[location].botId });
|
||||||
const bot = await Bot.findOne({ _id: req[location].botId });
|
|
||||||
|
if (!bot) {
|
||||||
if (!bot) {
|
return next(AccountNotFoundError({message: 'Failed to locate Bot account'}))
|
||||||
throw new Error('Failed to find bot');
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: bot.workspace.toString(),
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
req.bot = bot;
|
|
||||||
|
|
||||||
next();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed bot authorization'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: bot.workspace.toString(),
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
req.bot = bot;
|
||||||
|
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Bot, Integration, IntegrationAuth, Membership } from '../models';
|
import { Integration, IntegrationAuth } from '../models';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request is a member of workspace with proper roles associated
|
* Validate if user on request is a member of workspace with proper roles associated
|
||||||
@@ -21,48 +21,40 @@ const requireIntegrationAuth = ({
|
|||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// integration authorization middleware
|
// integration authorization middleware
|
||||||
|
|
||||||
try {
|
const { integrationId } = req.params;
|
||||||
const { integrationId } = req.params;
|
|
||||||
|
|
||||||
// validate integration accessibility
|
// validate integration accessibility
|
||||||
const integration = await Integration.findOne({
|
const integration = await Integration.findOne({
|
||||||
_id: integrationId
|
_id: integrationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integration) {
|
if (!integration) {
|
||||||
throw new Error('Failed to find integration');
|
return next(IntegrationNotFoundError({message: 'Failed to locate Integration'}))
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: integration.workspace.toString(),
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findOne({
|
|
||||||
_id: integration.integrationAuth
|
|
||||||
}).select(
|
|
||||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!integrationAuth) {
|
|
||||||
throw new Error('Failed to find integration authorization');
|
|
||||||
}
|
|
||||||
|
|
||||||
req.integration = integration;
|
|
||||||
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
|
||||||
integrationAuthId: integrationAuth._id.toString()
|
|
||||||
});
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed integration authorization'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: integration.workspace.toString(),
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
const integrationAuth = await IntegrationAuth.findOne({
|
||||||
|
_id: integration.integrationAuth
|
||||||
|
}).select(
|
||||||
|
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!integrationAuth) {
|
||||||
|
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.integration = integration;
|
||||||
|
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
||||||
|
integrationAuthId: integrationAuth._id.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Request, Response, NextFunction } from 'express';
|
|||||||
import { IntegrationAuth } from '../models';
|
import { IntegrationAuth } from '../models';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request is a member of workspace with proper roles associated
|
* Validate if user on request is a member of workspace with proper roles associated
|
||||||
@@ -22,41 +23,33 @@ const requireIntegrationAuthorizationAuth = ({
|
|||||||
attachAccessToken?: boolean;
|
attachAccessToken?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { integrationAuthId } = req.params;
|
||||||
const { integrationAuthId } = req.params;
|
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findOne({
|
const integrationAuth = await IntegrationAuth.findOne({
|
||||||
_id: integrationAuthId
|
_id: integrationAuthId
|
||||||
}).select(
|
}).select(
|
||||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!integrationAuth) {
|
if (!integrationAuth) {
|
||||||
throw new Error('Failed to find integration authorization');
|
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authorization credentials'}))
|
||||||
}
|
}
|
||||||
|
|
||||||
await validateMembership({
|
await validateMembership({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user._id.toString(),
|
||||||
workspaceId: integrationAuth.workspace.toString(),
|
workspaceId: integrationAuth.workspace.toString(),
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses
|
acceptedStatuses
|
||||||
});
|
});
|
||||||
|
|
||||||
req.integrationAuth = integrationAuth;
|
req.integrationAuth = integrationAuth;
|
||||||
if (attachAccessToken) {
|
if (attachAccessToken) {
|
||||||
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
||||||
integrationAuthId: integrationAuth._id.toString()
|
integrationAuthId: integrationAuth._id.toString()
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed (authorization) integration authorizationt'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { IOrganization, MembershipOrg } from '../models';
|
import { IOrganization, MembershipOrg } from '../models';
|
||||||
|
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request is a member with proper roles for organization
|
* Validate if user on request is a member with proper roles for organization
|
||||||
@@ -19,35 +19,28 @@ const requireOrganizationAuth = ({
|
|||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// organization authorization middleware
|
// organization authorization middleware
|
||||||
|
|
||||||
try {
|
// validate organization membership
|
||||||
// validate organization membership
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
user: req.user._id,
|
||||||
user: req.user._id,
|
organization: req.params.organizationId
|
||||||
organization: req.params.organizationId
|
}).populate<{ organization: IOrganization }>('organization');
|
||||||
}).populate<{ organization: IOrganization }>('organization');
|
|
||||||
|
|
||||||
if (!membershipOrg) {
|
|
||||||
throw new Error('Failed to find organization membership');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!acceptedRoles.includes(membershipOrg.role)) {
|
if (!membershipOrg) {
|
||||||
throw new Error('Failed to validate organization membership role');
|
return next(UnauthorizedRequestError({message: "You're not a member of this Organization."}))
|
||||||
}
|
|
||||||
|
|
||||||
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
|
||||||
throw new Error('Failed to validate organization membership status');
|
|
||||||
}
|
|
||||||
|
|
||||||
req.membershipOrg = membershipOrg;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed organization authorization'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
//TODO is this important to validate? I mean is it possible to save wrong role to database or get wrong role from databse? - Zamion101
|
||||||
|
if (!acceptedRoles.includes(membershipOrg.role)) {
|
||||||
|
return next(ValidationError({message: 'Failed to validate Organization Membership Role'}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
||||||
|
return next(ValidationError({message: 'Failed to validate Organization Membership Status'}))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.membershipOrg = membershipOrg;
|
||||||
|
|
||||||
|
return next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { ServiceToken } from '../models';
|
import { ServiceToken } from '../models';
|
||||||
import { JWT_SERVICE_SECRET } from '../config';
|
import { JWT_SERVICE_SECRET } from '../config';
|
||||||
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -24,33 +24,27 @@ const requireServiceTokenAuth = async (
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
// JWT service token middleware
|
// JWT service token middleware
|
||||||
try {
|
|
||||||
if (!req.headers?.authorization)
|
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
|
||||||
throw new Error('Failed to locate authorization header');
|
if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
|
||||||
|
//TODO: Determine what is the actual Token Type for Service Token Authentication (ex. Bearer)
|
||||||
|
//if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(UnauthorizedRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
|
||||||
|
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||||
|
|
||||||
const token = req.headers.authorization.split(' ')[1];
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
|
jwt.verify(AUTH_TOKEN_VALUE, JWT_SERVICE_SECRET)
|
||||||
|
);
|
||||||
|
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
const serviceToken = await ServiceToken.findOne({
|
||||||
jwt.verify(token, JWT_SERVICE_SECRET)
|
_id: decodedToken.serviceTokenId
|
||||||
);
|
})
|
||||||
|
.populate('user', '+publicKey')
|
||||||
|
.select('+encryptedKey +publicKey +nonce');
|
||||||
|
|
||||||
const serviceToken = await ServiceToken.findOne({
|
if (!serviceToken) return next(UnauthorizedRequestError({message: 'The service token does not match the record in the database'}))
|
||||||
_id: decodedToken.serviceTokenId
|
|
||||||
})
|
|
||||||
.populate('user', '+publicKey')
|
|
||||||
.select('+encryptedKey +publicKey +nonce');
|
|
||||||
|
|
||||||
if (!serviceToken) throw new Error('Failed to find service token');
|
req.serviceToken = serviceToken;
|
||||||
|
return next();
|
||||||
req.serviceToken = serviceToken;
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed to authenticate service token'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default requireServiceTokenAuth;
|
export default requireServiceTokenAuth;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { User } from '../models';
|
import { User } from '../models';
|
||||||
import { JWT_SIGNUP_SECRET } from '../config';
|
import { JWT_SIGNUP_SECRET } from '../config';
|
||||||
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -21,32 +21,24 @@ const requireSignupAuth = async (
|
|||||||
) => {
|
) => {
|
||||||
// JWT (temporary) authentication middleware for complete signup
|
// JWT (temporary) authentication middleware for complete signup
|
||||||
|
|
||||||
try {
|
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
|
||||||
if (!req.headers?.authorization)
|
if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
|
||||||
throw new Error('Failed to locate authorization header');
|
if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
|
||||||
|
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||||
|
|
||||||
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
|
jwt.verify(AUTH_TOKEN_VALUE, JWT_SIGNUP_SECRET)
|
||||||
|
);
|
||||||
|
|
||||||
const token = req.headers.authorization.split(' ')[1];
|
const user = await User.findOne({
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
_id: decodedToken.userId
|
||||||
jwt.verify(token, JWT_SIGNUP_SECRET)
|
}).select('+publicKey');
|
||||||
);
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
if (!user)
|
||||||
_id: decodedToken.userId
|
return next(UnauthorizedRequestError({message: 'Unable to authenticate for User account completion. Try logging in again'}))
|
||||||
}).select('+publicKey');
|
|
||||||
|
|
||||||
if (!user)
|
req.user = user;
|
||||||
throw new Error('Failed to temporarily authenticate unfound user');
|
return next();
|
||||||
|
|
||||||
req.user = user;
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error:
|
|
||||||
'Failed to temporarily authenticate user for complete account. Try logging in'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default requireSignupAuth;
|
export default requireSignupAuth;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -36,11 +36,7 @@ const requireWorkspaceAuth = ({
|
|||||||
|
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
return next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: 'Failed workspace authorization'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { validationResult } from 'express-validator';
|
import { validationResult } from 'express-validator';
|
||||||
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate intended inputs on [req] via express-validator
|
* Validate intended inputs on [req] via express-validator
|
||||||
@@ -15,16 +15,12 @@ const validate = (req: Request, res: Response, next: NextFunction) => {
|
|||||||
try {
|
try {
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
return res.status(400).json({ errors: errors.array() });
|
return next(BadRequestError({context: {errors: errors.array}}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
return next(UnauthorizedRequestError({message: 'Unauthenticated requests are not allowed. Try logging in'}))
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(401).send({
|
|
||||||
error: "Looks like you're unauthenticated . Try logging in"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +1,83 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
ENV_DEV,
|
ENV_DEV,
|
||||||
ENV_TESTING,
|
ENV_TESTING,
|
||||||
ENV_STAGING,
|
ENV_STAGING,
|
||||||
ENV_PROD,
|
ENV_PROD,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_GITHUB
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
export interface IIntegration {
|
export interface IIntegration {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: 'dev' | 'test' | 'staging' | 'prod';
|
environment: 'dev' | 'test' | 'staging' | 'prod';
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
app: string;
|
app: string;
|
||||||
target: string;
|
target: string;
|
||||||
context: string;
|
context: string;
|
||||||
siteId: string;
|
siteId: string;
|
||||||
integration: 'heroku' | 'vercel' | 'netlify';
|
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
|
||||||
integrationAuth: Types.ObjectId;
|
integrationAuth: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const integrationSchema = new Schema<IIntegration>(
|
const integrationSchema = new Schema<IIntegration>(
|
||||||
{
|
{
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Workspace',
|
ref: 'Workspace',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
|
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isActive: {
|
isActive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
app: { // name of app in provider
|
app: {
|
||||||
type: String,
|
// name of app in provider
|
||||||
default: null
|
type: String,
|
||||||
},
|
default: null
|
||||||
target: { // vercel-specific target (environment)
|
},
|
||||||
type: String,
|
target: {
|
||||||
default: null
|
// vercel-specific target (environment)
|
||||||
},
|
type: String,
|
||||||
context: { // netlify-specific context (deploy)
|
default: null
|
||||||
type: String,
|
},
|
||||||
default: null
|
context: {
|
||||||
},
|
// netlify-specific context (deploy)
|
||||||
siteId: { // netlify-specific site (app) id
|
type: String,
|
||||||
type: String,
|
default: null
|
||||||
default: null
|
},
|
||||||
},
|
siteId: {
|
||||||
integration: {
|
// netlify-specific site (app) id
|
||||||
type: String,
|
type: String,
|
||||||
enum: [
|
default: null
|
||||||
INTEGRATION_HEROKU,
|
},
|
||||||
INTEGRATION_VERCEL,
|
integration: {
|
||||||
INTEGRATION_NETLIFY
|
type: String,
|
||||||
],
|
enum: [
|
||||||
required: true
|
INTEGRATION_HEROKU,
|
||||||
},
|
INTEGRATION_VERCEL,
|
||||||
integrationAuth: {
|
INTEGRATION_NETLIFY,
|
||||||
type: Schema.Types.ObjectId,
|
INTEGRATION_GITHUB
|
||||||
ref: 'IntegrationAuth',
|
],
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
},
|
integrationAuth: {
|
||||||
{
|
type: Schema.Types.ObjectId,
|
||||||
timestamps: true
|
ref: 'IntegrationAuth',
|
||||||
}
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const Integration = model<IIntegration>('Integration', integrationSchema);
|
const Integration = model<IIntegration>('Integration', integrationSchema);
|
||||||
|
|||||||
@@ -1,83 +1,87 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_GITHUB
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
export interface IIntegrationAuth {
|
export interface IIntegrationAuth {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
integration: 'heroku' | 'vercel' | 'netlify';
|
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
|
||||||
teamId: string;
|
teamId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
refreshCiphertext?: string;
|
refreshCiphertext?: string;
|
||||||
refreshIV?: string;
|
refreshIV?: string;
|
||||||
refreshTag?: string;
|
refreshTag?: string;
|
||||||
accessCiphertext?: string;
|
accessCiphertext?: string;
|
||||||
accessIV?: string;
|
accessIV?: string;
|
||||||
accessTag?: string;
|
accessTag?: string;
|
||||||
accessExpiresAt?: Date;
|
accessExpiresAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||||
{
|
{
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
integration: {
|
integration: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [
|
enum: [
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
],
|
INTEGRATION_GITHUB
|
||||||
required: true
|
],
|
||||||
},
|
required: true
|
||||||
teamId: { // vercel-specific integration param
|
},
|
||||||
type: String
|
teamId: {
|
||||||
},
|
// vercel-specific integration param
|
||||||
accountId: { // netlify-specific integration param
|
type: String
|
||||||
type: String
|
},
|
||||||
},
|
accountId: {
|
||||||
refreshCiphertext: {
|
// netlify-specific integration param
|
||||||
type: String,
|
type: String
|
||||||
select: false
|
},
|
||||||
},
|
refreshCiphertext: {
|
||||||
refreshIV: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
refreshIV: {
|
||||||
refreshTag: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
refreshTag: {
|
||||||
accessCiphertext: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
accessCiphertext: {
|
||||||
accessIV: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
accessIV: {
|
||||||
accessTag: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
accessTag: {
|
||||||
accessExpiresAt: {
|
type: String,
|
||||||
type: Date,
|
select: false
|
||||||
select: false
|
},
|
||||||
}
|
accessExpiresAt: {
|
||||||
},
|
type: Date,
|
||||||
{
|
select: false
|
||||||
timestamps: true
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const IntegrationAuth = model<IIntegrationAuth>(
|
const IntegrationAuth = model<IIntegrationAuth>(
|
||||||
'IntegrationAuth',
|
'IntegrationAuth',
|
||||||
integrationAuthSchema
|
integrationAuthSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
export default IntegrationAuth;
|
export default IntegrationAuth;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
|
|
||||||
export interface ISecret {
|
export interface ISecret {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
|
version: number;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
type: string;
|
type: string;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
@@ -22,10 +23,18 @@ export interface ISecret {
|
|||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
secretValueHash: string;
|
secretValueHash: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
secretCommentHash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretSchema = new Schema<ISecret>(
|
const secretSchema = new Schema<ISecret>(
|
||||||
{
|
{
|
||||||
|
version: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Workspace',
|
ref: 'Workspace',
|
||||||
@@ -77,6 +86,22 @@ const secretSchema = new Schema<ISecret>(
|
|||||||
secretValueHash: {
|
secretValueHash: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
secretCommentCiphertext: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
secretCommentIV: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
secretCommentTag: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
secretCommentHash: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { authController } from '../controllers';
|
import { authController } from '../../controllers/v1';
|
||||||
import { loginLimiter } from '../helpers/rateLimiter';
|
import { loginLimiter } from '../../helpers/rateLimiter';
|
||||||
|
|
||||||
router.post('/token', validateRequest, authController.getNewToken);
|
router.post('/token', validateRequest, authController.getNewToken);
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
requireBotAuth,
|
requireBotAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { botController } from '../controllers';
|
import { botController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
@@ -4,10 +4,10 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireIntegrationAuth,
|
requireIntegrationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { integrationController } from '../controllers';
|
import { integrationController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
'/:integrationId',
|
'/:integrationId',
|
||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
||||||
import { integrationAuthController } from '../controllers';
|
import { integrationAuthController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/integration-options',
|
'/integration-options',
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipOrgController } from '../controllers';
|
import { membershipOrgController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/signup',
|
'/signup',
|
||||||
@@ -4,10 +4,10 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
import { keyController } from '../controllers';
|
import { keyController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
@@ -34,6 +34,4 @@ router.get(
|
|||||||
keyController.getLatestKey
|
keyController.getLatestKey
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipController } from '../controllers';
|
import { membershipController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get( // used for CLI (deprecate)
|
router.get( // used for CLI (deprecate)
|
||||||
'/:workspaceId/connect',
|
'/:workspaceId/connect',
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { param } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipOrgController } from '../controllers';
|
import { membershipOrgController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
// TODO
|
// TODO
|
||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../variables';
|
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
|
||||||
import { organizationController } from '../controllers';
|
import { organizationController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, requireSignupAuth, validateRequest } from '../middleware';
|
import { requireAuth, requireSignupAuth, validateRequest } from '../../middleware';
|
||||||
import { passwordController } from '../controllers';
|
import { passwordController } from '../../controllers/v1';
|
||||||
import { passwordLimiter } from '../helpers/rateLimiter';
|
import { passwordLimiter } from '../../helpers/rateLimiter';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/srp1',
|
'/srp1',
|
||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { body, query, param } from 'express-validator';
|
import { body, query, param } from 'express-validator';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { secretController } from '../../controllers/v1';
|
||||||
import { secretController } from '../controllers';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
||||||
import { serviceTokenController } from '../controllers';
|
import { serviceTokenController } from '../../controllers/v1';
|
||||||
|
|
||||||
// TODO: revoke service token
|
// TODO: revoke service token
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireSignupAuth, validateRequest } from '../middleware';
|
import { requireSignupAuth, validateRequest } from '../../middleware';
|
||||||
import { signupController } from '../controllers';
|
import { signupController } from '../../controllers/v1';
|
||||||
import { signupLimiter } from '../helpers/rateLimiter';
|
import { signupLimiter } from '../../helpers/rateLimiter';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/email/signup',
|
'/email/signup',
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { stripeController } from '../controllers';
|
import { stripeController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post('/webhook', stripeController.handleWebhook);
|
router.post('/webhook', stripeController.handleWebhook);
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { requireAuth } from '../middleware';
|
import { requireAuth } from '../../middleware';
|
||||||
import { userController } from '../controllers';
|
import { userController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get('/', requireAuth, userController.getUser);
|
router.get('/', requireAuth, userController.getUser);
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { body, query } from 'express-validator';
|
import { body, query } from 'express-validator';
|
||||||
import { userActionController } from '../controllers';
|
import { userActionController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body, param } from 'express-validator';
|
import { body, param, query } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
import { workspaceController, membershipController } from '../controllers';
|
import { workspaceController, membershipController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/keys',
|
'/:workspaceId/keys',
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import secret from './secret';
|
||||||
|
import workspace from './workspace';
|
||||||
|
|
||||||
|
export {
|
||||||
|
secret,
|
||||||
|
workspace
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import { body, param, query } from 'express-validator';
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
requireServiceTokenAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../middleware';
|
||||||
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
import { membershipController } from '../../controllers/v1';
|
||||||
|
import { workspaceController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/keys',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspacePublicKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/users',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceMemberships
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get('/', requireAuth, workspaceController.getWorkspaces);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
requireAuth,
|
||||||
|
body('workspaceName').exists().trim().notEmpty(),
|
||||||
|
body('organizationId').exists().trim().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.createWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:workspaceId',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.deleteWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:workspaceId/name',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
body('name').exists().trim().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.changeWorkspaceName
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:workspaceId/invite-signup',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
body('email').exists().trim().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
membershipController.inviteUserToWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/integrations',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceIntegrations
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/authorizations',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceIntegrationAuthorizations
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get( // TODO: modify
|
||||||
|
'/:workspaceId/service-tokens',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceServiceTokens
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:workspaceId/secrets',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
body('secrets').exists(),
|
||||||
|
body('keys').exists(),
|
||||||
|
body('environment').exists().trim().notEmpty(),
|
||||||
|
body('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pushWorkspaceSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/secrets',
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
query('environment').exists().trim(),
|
||||||
|
query('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pullSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get( // TODO: modify based on upcoming serviceTokenData changes
|
||||||
|
'/:workspaceId/secrets-service-token',
|
||||||
|
requireServiceTokenAuth,
|
||||||
|
query('environment').exists().trim(),
|
||||||
|
query('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pullSecretsServiceToken
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -5,8 +5,16 @@ import {
|
|||||||
POSTHOG_PROJECT_API_KEY,
|
POSTHOG_PROJECT_API_KEY,
|
||||||
TELEMETRY_ENABLED
|
TELEMETRY_ENABLED
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
import { getLogger } from '../utils/logger';
|
||||||
|
|
||||||
console.log('TELEMETRY_ENABLED: ', TELEMETRY_ENABLED);
|
if(TELEMETRY_ENABLED){
|
||||||
|
getLogger("backend-main").info([
|
||||||
|
"",
|
||||||
|
"Infisical collects telemetry data about general usage.",
|
||||||
|
"The data helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth for investors as we support Infisical as open-source software.",
|
||||||
|
"To opt out of telemetry, you can set `TELEMETRY_ENABLED=false` within the environment variables",
|
||||||
|
].join('\n'))
|
||||||
|
}
|
||||||
|
|
||||||
let postHogClient: any;
|
let postHogClient: any;
|
||||||
if (NODE_ENV === 'production' && TELEMETRY_ENABLED) {
|
if (NODE_ENV === 'production' && TELEMETRY_ENABLED) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { getLogger } from '../utils/logger';
|
||||||
|
|
||||||
|
export const initDatabase = (MONGO_URL: string) => {
|
||||||
|
mongoose
|
||||||
|
.connect(MONGO_URL)
|
||||||
|
.then(() => getLogger("database").info("Database connection established"))
|
||||||
|
.catch((e) => getLogger("database").error(`Unable to establish Database connection due to the error.\n${e}`));
|
||||||
|
return mongoose.connection;
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { createTerminus } from '@godaddy/terminus';
|
||||||
|
import { getLogger } from '../utils/logger';
|
||||||
|
|
||||||
|
export const setUpHealthEndpoint = <T>(server: T) => {
|
||||||
|
const onSignal = () => {
|
||||||
|
getLogger('backend-main').info('Server is starting clean-up');
|
||||||
|
return Promise.all([
|
||||||
|
new Promise((resolve) => {
|
||||||
|
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
||||||
|
mongoose.connection.close()
|
||||||
|
.then(() => resolve('Database connection closed'));
|
||||||
|
} else {
|
||||||
|
resolve('Database connection already closed');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const healthCheck = () => {
|
||||||
|
// `state.isShuttingDown` (boolean) shows whether the server is shutting down or not
|
||||||
|
// optionally include a resolve value to be included as info in the health check response
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
createTerminus(server, {
|
||||||
|
healthChecks: {
|
||||||
|
'/healthcheck': healthCheck,
|
||||||
|
onSignal
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config';
|
||||||
|
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from '../variables';
|
||||||
|
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
|
const mailOpts: SMTPConnection.Options = {
|
||||||
|
host: SMTP_HOST,
|
||||||
|
port: SMTP_PORT as number
|
||||||
|
};
|
||||||
|
|
||||||
|
if (SMTP_USERNAME && SMTP_PASSWORD) {
|
||||||
|
mailOpts.auth = {
|
||||||
|
user: SMTP_USERNAME,
|
||||||
|
pass: SMTP_PASSWORD
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SMTP_SECURE) {
|
||||||
|
switch (SMTP_HOST) {
|
||||||
|
case SMTP_HOST_SENDGRID:
|
||||||
|
mailOpts.requireTLS = true;
|
||||||
|
break;
|
||||||
|
case SMTP_HOST_MAILGUN:
|
||||||
|
mailOpts.requireTLS = true;
|
||||||
|
mailOpts.tls = {
|
||||||
|
ciphers: 'TLSv1.2'
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mailOpts.secure = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initSmtp = () => {
|
||||||
|
const transporter = nodemailer.createTransport(mailOpts);
|
||||||
|
transporter
|
||||||
|
.verify()
|
||||||
|
.then(() => {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureMessage('SMTP - Successfully connected');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(
|
||||||
|
`SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return transporter;
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user