Merge branch 'main' of https://github.com/Infisical/infisical
@@ -1,7 +1,5 @@
|
|||||||
# Keys
|
# Keys
|
||||||
# Required keys for platform encryption/decryption ops
|
# Required key for platform encryption/decryption ops
|
||||||
PRIVATE_KEY=replace_with_nacl_sk
|
|
||||||
PUBLIC_KEY=replace_with_nacl_pk
|
|
||||||
ENCRYPTION_KEY=replace_with_lengthy_secure_hex
|
ENCRYPTION_KEY=replace_with_lengthy_secure_hex
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
@@ -9,13 +7,13 @@ ENCRYPTION_KEY=replace_with_lengthy_secure_hex
|
|||||||
JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex
|
JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex
|
||||||
JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex
|
JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex
|
||||||
JWT_AUTH_SECRET=replace_with_lengthy_secure_hex
|
JWT_AUTH_SECRET=replace_with_lengthy_secure_hex
|
||||||
|
JWT_SERVICE_SECRET=replace_with_lengthy_secure_hex
|
||||||
|
|
||||||
# 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
|
||||||
# describing a time span (e.g. 60, "2 days", "10h", "7d")
|
# describing a time span (e.g. 60, "2 days", "10h", "7d")
|
||||||
JWT_AUTH_LIFETIME=
|
JWT_AUTH_LIFETIME=
|
||||||
JWT_REFRESH_LIFETIME=
|
JWT_REFRESH_LIFETIME=
|
||||||
JWT_SERVICE_SECRET=
|
|
||||||
JWT_SIGNUP_LIFETIME=
|
JWT_SIGNUP_LIFETIME=
|
||||||
|
|
||||||
# Optional lifetimes for OTP expressed in seconds
|
# Optional lifetimes for OTP expressed in seconds
|
||||||
@@ -33,26 +31,31 @@ 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
|
# Required to send emails
|
||||||
# By default, SMTP_HOST is set to smtp.gmail.com
|
# By default, SMTP_HOST is set to smtp.gmail.com, SMTP_PORT is set to 587, SMTP_TLS is set to false, and SMTP_FROM_NAME is set to Infisical
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
# If STARTTLS is supported, the connection will be upgraded to TLS when SMTP_SECURE is set to false
|
||||||
|
SMTP_SECURE=false
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_NAME=Team
|
SMTP_USERNAME=
|
||||||
SMTP_USERNAME=[email protected]
|
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
|
SMTP_FROM_ADDRESS=
|
||||||
|
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 }}
|
|
||||||
@@ -25,7 +25,9 @@ node_modules
|
|||||||
.env
|
.env
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
coverage
|
||||||
|
reports
|
||||||
|
junit.xml
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ nfpms:
|
|||||||
- rpm
|
- rpm
|
||||||
- deb
|
- deb
|
||||||
- apk
|
- apk
|
||||||
|
- archlinux
|
||||||
bindir: /usr/bin
|
bindir: /usr/bin
|
||||||
scoop:
|
scoop:
|
||||||
bucket:
|
bucket:
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Vercel (https://github.com/Infisical/infisical/issues/60)
|
<a href="https://infisical.com/docs/integrations/cloud/vercel?ref=github.com">
|
||||||
|
✔️ Vercel
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
<a href="https://infisical.com/docs/integrations/platforms/kubernetes?ref=github.com">
|
<a href="https://infisical.com/docs/integrations/platforms/kubernetes?ref=github.com">
|
||||||
@@ -144,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
|
||||||
@@ -155,10 +159,10 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
🔜 GCP
|
🔜 GCP
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 GitLab CI/CD
|
🔜 GitLab CI/CD (https://github.com/Infisical/infisical/issues/134)
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 CircleCI
|
🔜 CircleCI (https://github.com/Infisical/infisical/issues/91)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -177,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
|
||||||
@@ -191,7 +197,7 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
🔜 Supabase
|
🔜 Supabase
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Serverless
|
🔜 Render (https://github.com/Infisical/infisical/issues/132)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -315,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/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/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/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/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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,8 +22,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;
|
||||||
|
|||||||
@@ -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,11 +17,12 @@
|
|||||||
"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.0",
|
||||||
"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",
|
||||||
@@ -37,7 +40,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 +58,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,74 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
} from './routes';
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
export const server = app.listen(PORT, () => {
|
||||||
|
console.log(`Listening on PORT ${[PORT]}`);
|
||||||
|
});
|
||||||
@@ -14,21 +14,24 @@ 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 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! || 'smtp.gmail.com';
|
||||||
|
const SMTP_SECURE = process.env.SMTP_SECURE! || false;
|
||||||
const SMTP_PORT = process.env.SMTP_PORT! || 587;
|
const SMTP_PORT = process.env.SMTP_PORT! || 587;
|
||||||
const SMTP_NAME = process.env.SMTP_NAME!;
|
|
||||||
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!;
|
||||||
@@ -53,20 +56,23 @@ export {
|
|||||||
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,
|
||||||
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,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -69,7 +69,7 @@ const getSecretsHelper = async ({
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
}) => {
|
}) => {
|
||||||
let content = {} as any;
|
const content = {} as any;
|
||||||
try {
|
try {
|
||||||
const key = await getKey({ workspaceId });
|
const key = await getKey({ workspaceId });
|
||||||
const secrets = await Secret.find({
|
const secrets = await Secret.find({
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ const handleOAuthExchangeHelper = async ({
|
|||||||
if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange');
|
if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange');
|
||||||
|
|
||||||
// exchange code for access and refresh tokens
|
// exchange code for access and refresh tokens
|
||||||
let res = await exchangeCode({
|
const res = await exchangeCode({
|
||||||
integration,
|
integration,
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
|
|
||||||
let update: Update = {
|
const update: Update = {
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
integration
|
integration
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
// to that integration
|
// to that integration
|
||||||
for await (const integration of integrations) {
|
for await (const integration of integrations) {
|
||||||
// get workspace, environment (shared) secrets
|
// get workspace, environment (shared) secrets
|
||||||
const secrets = await BotService.getSecrets({
|
const secrets = await BotService.getSecrets({ // issue here?
|
||||||
workspaceId: integration.workspace.toString(),
|
workspaceId: integration.workspace.toString(),
|
||||||
environment: integration.environment
|
environment: integration.environment
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -1,129 +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
|
|
||||||
} 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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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,58 @@
|
|||||||
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,
|
||||||
|
ACTION_PUSH_TO_HEROKU
|
||||||
} 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';
|
||||||
|
import { user } from '../routes';
|
||||||
|
|
||||||
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 +68,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
|
||||||
@@ -107,7 +124,7 @@ const exchangeCodeHeroku = async ({
|
|||||||
code: string;
|
code: string;
|
||||||
}) => {
|
}) => {
|
||||||
let res: ExchangeCodeHerokuResponse;
|
let res: ExchangeCodeHerokuResponse;
|
||||||
let accessExpiresAt = new Date();
|
const accessExpiresAt = new Date();
|
||||||
try {
|
try {
|
||||||
res = (await axios.post(
|
res = (await axios.post(
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
@@ -144,35 +161,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 +199,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,44 +13,44 @@ 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;
|
||||||
try {
|
try {
|
||||||
@@ -63,16 +63,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,47 +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:
|
||||||
} catch (err) {
|
await syncSecretsGitHub({
|
||||||
Sentry.setUser(null);
|
integration,
|
||||||
Sentry.captureException(err);
|
secrets,
|
||||||
throw new Error('Failed to sync secrets to integration');
|
accessToken
|
||||||
|
});
|
||||||
|
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]
|
||||||
@@ -75,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]
|
||||||
@@ -174,9 +188,9 @@ const syncSecretsVercel = async ({
|
|||||||
[secret.key]: secret
|
[secret.key]: secret
|
||||||
}), {});
|
}), {});
|
||||||
|
|
||||||
let updateSecrets: VercelSecret[] = [];
|
const updateSecrets: VercelSecret[] = [];
|
||||||
let deleteSecrets: VercelSecret[] = [];
|
const deleteSecrets: VercelSecret[] = [];
|
||||||
let newSecrets: VercelSecret[] = [];
|
const newSecrets: VercelSecret[] = [];
|
||||||
|
|
||||||
// Identify secrets to create
|
// Identify secrets to create
|
||||||
Object.keys(secrets).map((key) => {
|
Object.keys(secrets).map((key) => {
|
||||||
@@ -287,8 +301,24 @@ const syncSecretsNetlify = async ({
|
|||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
interface NetlifyValue {
|
||||||
|
id?: string;
|
||||||
|
context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production',
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetlifySecret {
|
||||||
|
key: string;
|
||||||
|
values: NetlifyValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetlifySecretsRes {
|
||||||
|
[index: string]: NetlifySecret;
|
||||||
|
}
|
||||||
|
|
||||||
const getParams = new URLSearchParams({
|
const getParams = new URLSearchParams({
|
||||||
context_name: integration.context,
|
context_name: 'all', // integration.context or all
|
||||||
site_id: integration.siteId
|
site_id: integration.siteId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -304,71 +334,94 @@ const syncSecretsNetlify = async ({
|
|||||||
.data
|
.data
|
||||||
.reduce((obj: any, secret: any) => ({
|
.reduce((obj: any, secret: any) => ({
|
||||||
...obj,
|
...obj,
|
||||||
[secret.key]: secret.values[0].value
|
[secret.key]: secret
|
||||||
}), {});
|
}), {});
|
||||||
|
|
||||||
interface UpdateNetlifySecret {
|
const newSecrets: NetlifySecret[] = []; // createEnvVars
|
||||||
key: string;
|
const deleteSecrets: string[] = []; // deleteEnvVar
|
||||||
context: string;
|
const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue
|
||||||
value: string;
|
const updateSecrets: NetlifySecret[] = []; // setEnvVarValue
|
||||||
}
|
|
||||||
|
|
||||||
interface DeleteNetlifySecret {
|
|
||||||
key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NewNetlifySecretValue {
|
|
||||||
value: string;
|
|
||||||
context: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NewNetlifySecret {
|
|
||||||
key: string;
|
|
||||||
values: NewNetlifySecretValue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateSecrets: UpdateNetlifySecret[] = [];
|
|
||||||
let deleteSecrets: DeleteNetlifySecret[] = [];
|
|
||||||
let newSecrets: NewNetlifySecret[] = [];
|
|
||||||
|
|
||||||
// Identify secrets to create
|
// identify secrets to create and update
|
||||||
Object.keys(secrets).map((key) => {
|
Object.keys(secrets).map((key) => {
|
||||||
if (!(key in res)) {
|
if (!(key in res)) {
|
||||||
// case: secret has been created
|
// case: Infisical secret does not exist in Netlify -> create secret
|
||||||
newSecrets.push({
|
newSecrets.push({
|
||||||
key: key,
|
key,
|
||||||
values: [{
|
values: [{
|
||||||
value: secrets[key], // include id?
|
value: secrets[key],
|
||||||
context: integration.context
|
context: integration.context
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
});
|
// case: Infisical secret exists in Netlify
|
||||||
|
const contexts = res[key].values
|
||||||
// Identify secrets to update and delete
|
.reduce((obj: any, value: NetlifyValue) => ({
|
||||||
Object.keys(res).map((key) => {
|
...obj,
|
||||||
if (key in secrets) {
|
[value.context]: value
|
||||||
if (res[key] !== secrets[key]) {
|
}), {});
|
||||||
// case: secret value has changed
|
|
||||||
|
if (integration.context in contexts) {
|
||||||
|
// case: Netlify secret value exists in integration context
|
||||||
|
if (secrets[key] !== contexts[integration.context].value) {
|
||||||
|
// case: Infisical and Netlify secret values are different
|
||||||
|
// -> update Netlify secret context and value
|
||||||
|
updateSecrets.push({
|
||||||
|
key,
|
||||||
|
values: [{
|
||||||
|
context: integration.context,
|
||||||
|
value: secrets[key]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// case: Netlify secret value does not exist in integration context
|
||||||
|
// -> add the new Netlify secret context and value
|
||||||
updateSecrets.push({
|
updateSecrets.push({
|
||||||
key: key,
|
key,
|
||||||
context: integration.context,
|
values: [{
|
||||||
value: secrets[key]
|
context: integration.context,
|
||||||
|
value: secrets[key]
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// case: secret has been deleted
|
})
|
||||||
deleteSecrets.push({
|
|
||||||
key
|
// identify secrets to delete
|
||||||
|
// TODO: revise (patch case where 1 context was deleted but others still there
|
||||||
|
Object.keys(res).map((key) => {
|
||||||
|
// loop through each key's context
|
||||||
|
if (!(key in secrets)) {
|
||||||
|
// case: Netlify secret does not exist in Infisical
|
||||||
|
|
||||||
|
const numberOfValues = res[key].values.length;
|
||||||
|
|
||||||
|
res[key].values.forEach((value: NetlifyValue) => {
|
||||||
|
if (value.context === integration.context) {
|
||||||
|
if (numberOfValues <= 1) {
|
||||||
|
// case: Netlify secret value has less than 1 context -> delete secret
|
||||||
|
deleteSecrets.push(key);
|
||||||
|
} else {
|
||||||
|
// case: Netlify secret value has more than 1 context -> delete secret value context
|
||||||
|
deleteSecretValues.push({
|
||||||
|
key,
|
||||||
|
values: [{
|
||||||
|
id: value.id,
|
||||||
|
context: integration.context,
|
||||||
|
value: value.value
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncParams = new URLSearchParams({
|
const syncParams = new URLSearchParams({
|
||||||
site_id: integration.siteId
|
site_id: integration.siteId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sync/push new secrets
|
|
||||||
if (newSecrets.length > 0) {
|
if (newSecrets.length > 0) {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
|
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
|
||||||
@@ -382,15 +435,13 @@ const syncSecretsNetlify = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync/push updated secrets
|
|
||||||
if (updateSecrets.length > 0) {
|
if (updateSecrets.length > 0) {
|
||||||
|
updateSecrets.forEach(async (secret: NetlifySecret) => {
|
||||||
updateSecrets.forEach(async (secret: UpdateNetlifySecret) => {
|
|
||||||
await axios.patch(
|
await axios.patch(
|
||||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
|
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
|
||||||
{
|
{
|
||||||
context: secret.context,
|
context: secret.values[0].context,
|
||||||
value: secret.value
|
value: secret.values[0].value
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
params: syncParams,
|
params: syncParams,
|
||||||
@@ -402,11 +453,24 @@ const syncSecretsNetlify = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete secrets
|
|
||||||
if (deleteSecrets.length > 0) {
|
if (deleteSecrets.length > 0) {
|
||||||
deleteSecrets.forEach(async (secret: DeleteNetlifySecret) => {
|
deleteSecrets.forEach(async (key: string) => {
|
||||||
await axios.delete(
|
await axios.delete(
|
||||||
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
|
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`,
|
||||||
|
{
|
||||||
|
params: syncParams,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteSecretValues.length > 0) {
|
||||||
|
deleteSecretValues.forEach(async (secret: NetlifySecret) => {
|
||||||
|
await axios.delete(
|
||||||
|
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`,
|
||||||
{
|
{
|
||||||
params: syncParams,
|
params: syncParams,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -416,7 +480,6 @@ const syncSecretsNetlify = async ({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
@@ -424,6 +487,119 @@ const syncSecretsNetlify = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 };
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -34,6 +34,4 @@ router.get(
|
|||||||
keyController.getLatestKey
|
keyController.getLatestKey
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
export const initDatabase = (MONGO_URL: string) => {
|
||||||
|
mongoose
|
||||||
|
.connect(MONGO_URL)
|
||||||
|
.then(() => console.log('Successfully connected to DB'))
|
||||||
|
.catch((e) => console.log('Failed to connect to DB ', e));
|
||||||
|
return mongoose.connection;
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import { createTerminus } from '@godaddy/terminus';
|
||||||
|
|
||||||
|
export const setUpHealthEndpoint = <T>(server: T) => {
|
||||||
|
const onSignal = () => {
|
||||||
|
console.log('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,34 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config';
|
||||||
|
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
|
const mailOpts: SMTPConnection.Options = {
|
||||||
|
host: SMTP_HOST,
|
||||||
|
secure: SMTP_SECURE as boolean,
|
||||||
|
port: SMTP_PORT as number
|
||||||
|
};
|
||||||
|
if (SMTP_USERNAME && SMTP_PASSWORD) {
|
||||||
|
mailOpts.auth = {
|
||||||
|
user: SMTP_USERNAME,
|
||||||
|
pass: SMTP_PASSWORD
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import nacl from 'tweetnacl';
|
import nacl from 'tweetnacl';
|
||||||
import util from 'tweetnacl-util';
|
import util from 'tweetnacl-util';
|
||||||
import AesGCM from './aes-gcm';
|
import AesGCM from './aes-gcm';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new base64, NaCl, public-private key pair.
|
* Return new base64, NaCl, public-private key pair.
|
||||||
@@ -47,6 +48,8 @@ const encryptAsymmetric = ({
|
|||||||
util.decodeBase64(privateKey)
|
util.decodeBase64(privateKey)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to perform asymmetric encryption');
|
throw new Error('Failed to perform asymmetric encryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +89,8 @@ const decryptAsymmetric = ({
|
|||||||
util.decodeBase64(privateKey)
|
util.decodeBase64(privateKey)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to perform asymmetric decryption');
|
throw new Error('Failed to perform asymmetric decryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +117,8 @@ const encryptSymmetric = ({
|
|||||||
iv = obj.iv;
|
iv = obj.iv;
|
||||||
tag = obj.tag;
|
tag = obj.tag;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to perform symmetric encryption');
|
throw new Error('Failed to perform symmetric encryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +154,8 @@ const decryptSymmetric = ({
|
|||||||
try {
|
try {
|
||||||
plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
|
plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to perform symmetric decryption');
|
throw new Error('Failed to perform symmetric decryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +1,74 @@
|
|||||||
import {
|
import {
|
||||||
ENV_DEV,
|
ENV_DEV,
|
||||||
ENV_TESTING,
|
ENV_TESTING,
|
||||||
ENV_STAGING,
|
ENV_STAGING,
|
||||||
ENV_PROD,
|
ENV_PROD,
|
||||||
ENV_SET
|
ENV_SET
|
||||||
} from './environment';
|
} from './environment';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_OAUTH2,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_OAUTH2,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_OPTIONS
|
INTEGRATION_VERCEL_API_URL,
|
||||||
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
|
INTEGRATION_GITHUB_API_URL,
|
||||||
|
INTEGRATION_OPTIONS
|
||||||
} from './integration';
|
} from './integration';
|
||||||
import {
|
import {
|
||||||
OWNER,
|
OWNER,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
INVITED,
|
INVITED,
|
||||||
ACCEPTED,
|
ACCEPTED,
|
||||||
COMPLETED,
|
COMPLETED,
|
||||||
GRANTED
|
GRANTED
|
||||||
} from './organization';
|
} from './organization';
|
||||||
import {
|
import { SECRET_SHARED, SECRET_PERSONAL } from './secret';
|
||||||
SECRET_SHARED,
|
import { PLAN_STARTER, PLAN_PRO } from './stripe';
|
||||||
SECRET_PERSONAL
|
import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event';
|
||||||
} from './secret';
|
import { ACTION_PUSH_TO_HEROKU } from './action';
|
||||||
import {
|
|
||||||
PLAN_STARTER,
|
|
||||||
PLAN_PRO
|
|
||||||
} from './stripe';
|
|
||||||
import {
|
|
||||||
EVENT_PUSH_SECRETS,
|
|
||||||
EVENT_PULL_SECRETS
|
|
||||||
} from './event';
|
|
||||||
import {
|
|
||||||
ACTION_PUSH_TO_HEROKU
|
|
||||||
} from './action';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
OWNER,
|
OWNER,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
INVITED,
|
INVITED,
|
||||||
ACCEPTED,
|
ACCEPTED,
|
||||||
COMPLETED,
|
COMPLETED,
|
||||||
GRANTED,
|
GRANTED,
|
||||||
PLAN_STARTER,
|
PLAN_STARTER,
|
||||||
PLAN_PRO,
|
PLAN_PRO,
|
||||||
SECRET_SHARED,
|
SECRET_SHARED,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
ENV_DEV,
|
ENV_DEV,
|
||||||
ENV_TESTING,
|
ENV_TESTING,
|
||||||
ENV_STAGING,
|
ENV_STAGING,
|
||||||
ENV_PROD,
|
ENV_PROD,
|
||||||
ENV_SET,
|
ENV_SET,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_OAUTH2,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_OAUTH2,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
EVENT_PUSH_SECRETS,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
EVENT_PULL_SECRETS,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
ACTION_PUSH_TO_HEROKU,
|
INTEGRATION_GITHUB_API_URL,
|
||||||
INTEGRATION_OPTIONS
|
EVENT_PUSH_SECRETS,
|
||||||
};
|
EVENT_PULL_SECRETS,
|
||||||
|
ACTION_PUSH_TO_HEROKU,
|
||||||
|
INTEGRATION_OPTIONS
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
CLIENT_ID_HEROKU,
|
CLIENT_ID_HEROKU,
|
||||||
CLIENT_ID_NETLIFY
|
CLIENT_ID_NETLIFY,
|
||||||
|
CLIENT_ID_GITHUB,
|
||||||
|
CLIENT_SLUG_VERCEL
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
// integrations
|
// integrations
|
||||||
const INTEGRATION_HEROKU = 'heroku';
|
const INTEGRATION_HEROKU = 'heroku';
|
||||||
const INTEGRATION_VERCEL = 'vercel';
|
const INTEGRATION_VERCEL = 'vercel';
|
||||||
const INTEGRATION_NETLIFY = 'netlify';
|
const INTEGRATION_NETLIFY = 'netlify';
|
||||||
|
const INTEGRATION_GITHUB = 'github';
|
||||||
const INTEGRATION_SET = new Set([
|
const INTEGRATION_SET = new Set([
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_GITHUB
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// integration types
|
// integration types
|
||||||
@@ -18,13 +22,17 @@ const INTEGRATION_OAUTH2 = 'oauth2';
|
|||||||
|
|
||||||
// integration oauth endpoints
|
// integration oauth endpoints
|
||||||
const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
|
||||||
const INTEGRATION_VERCEL_TOKEN_URL = 'https://api.vercel.com/v2/oauth/access_token';
|
const INTEGRATION_VERCEL_TOKEN_URL =
|
||||||
|
'https://api.vercel.com/v2/oauth/access_token';
|
||||||
const INTEGRATION_NETLIFY_TOKEN_URL = 'https://api.netlify.com/oauth/token';
|
const INTEGRATION_NETLIFY_TOKEN_URL = 'https://api.netlify.com/oauth/token';
|
||||||
|
const INTEGRATION_GITHUB_TOKEN_URL =
|
||||||
|
'https://github.com/login/oauth/access_token';
|
||||||
|
|
||||||
// integration apps endpoints
|
// integration apps endpoints
|
||||||
const INTEGRATION_HEROKU_API_URL = 'https://api.heroku.com';
|
const INTEGRATION_HEROKU_API_URL = 'https://api.heroku.com';
|
||||||
const INTEGRATION_VERCEL_API_URL = 'https://api.vercel.com';
|
const INTEGRATION_VERCEL_API_URL = 'https://api.vercel.com';
|
||||||
const INTEGRATION_NETLIFY_API_URL = 'https://api.netlify.com';
|
const INTEGRATION_NETLIFY_API_URL = 'https://api.netlify.com';
|
||||||
|
const INTEGRATION_GITHUB_API_URL = 'https://api.github.com';
|
||||||
|
|
||||||
const INTEGRATION_OPTIONS = [
|
const INTEGRATION_OPTIONS = [
|
||||||
{
|
{
|
||||||
@@ -43,6 +51,7 @@ const INTEGRATION_OPTIONS = [
|
|||||||
isAvailable: true,
|
isAvailable: true,
|
||||||
type: 'vercel',
|
type: 'vercel',
|
||||||
clientId: '',
|
clientId: '',
|
||||||
|
clientSlug: CLIENT_SLUG_VERCEL,
|
||||||
docsLink: ''
|
docsLink: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -54,6 +63,16 @@ const INTEGRATION_OPTIONS = [
|
|||||||
clientId: CLIENT_ID_NETLIFY,
|
clientId: CLIENT_ID_NETLIFY,
|
||||||
docsLink: ''
|
docsLink: ''
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'GitHub',
|
||||||
|
slug: 'github',
|
||||||
|
image: 'GitHub',
|
||||||
|
isAvailable: true,
|
||||||
|
type: 'oauth2',
|
||||||
|
clientId: CLIENT_ID_GITHUB,
|
||||||
|
docsLink: ''
|
||||||
|
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Google Cloud Platform',
|
name: 'Google Cloud Platform',
|
||||||
slug: 'gcp',
|
slug: 'gcp',
|
||||||
@@ -102,16 +121,19 @@ const INTEGRATION_OPTIONS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export {
|
export {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_OAUTH2,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_OAUTH2,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_OPTIONS
|
INTEGRATION_VERCEL_API_URL,
|
||||||
}
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
|
INTEGRATION_GITHUB_API_URL,
|
||||||
|
INTEGRATION_OPTIONS
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mongo-test:
|
||||||
|
image: mongo
|
||||||
|
container_name: infisical-test-mongo
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 27018:27017
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_ROOT_USERNAME=test
|
||||||
|
- MONGO_INITDB_ROOT_PASSWORD=test1234
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
|
process.env.MONGO_URL =
|
||||||
|
'mongodb://test:test1234@localhost:27018/?authSource=admin';
|
||||||
|
process.env.MONGO_USERNAME = 'test';
|
||||||
|
process.env.MONGO_PASSWORD = 'test1234';
|
||||||
@@ -8,16 +8,13 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"outDir": "build",
|
"outDir": "build",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"typeRoots" : ["./src/types", "./node_modules/@types"]
|
"typeRoots": ["./src/types", "./node_modules/@types"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*"
|
"exclude": ["node_modules"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@@ -19,12 +20,38 @@ import (
|
|||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command
|
||||||
var runCmd = &cobra.Command{
|
var runCmd = &cobra.Command{
|
||||||
|
Example: `
|
||||||
|
infisical run --env=dev -- npm run dev
|
||||||
|
infisical run --command "first-command && second-command; more-commands..."
|
||||||
|
`,
|
||||||
Use: "run [any infisical run command flags] -- [your application start command]",
|
Use: "run [any infisical run command flags] -- [your application start command]",
|
||||||
Short: "Used to inject environments variables into your application process",
|
Short: "Used to inject environments variables into your application process",
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Example: "infisical run --env=prod -- npm run dev",
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
PreRun: toggleDebug,
|
PreRun: toggleDebug,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Check if the --command flag has been set
|
||||||
|
commandFlagSet := cmd.Flags().Changed("command")
|
||||||
|
|
||||||
|
// If the --command flag has been set, check if a value was provided
|
||||||
|
if commandFlagSet {
|
||||||
|
command := cmd.Flag("command").Value.String()
|
||||||
|
if command == "" {
|
||||||
|
return fmt.Errorf("you need to provide a command after the flag --command")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the --command flag has been set, args should not be provided
|
||||||
|
if len(args) > 0 {
|
||||||
|
return fmt.Errorf("you cannot set any arguments after --command flag. --command only takes a string command")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the --command flag has not been set, at least one arg should be provided
|
||||||
|
if len(args) == 0 {
|
||||||
|
return fmt.Errorf("at least one argument is required after the run command, received %d", len(args))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
envName, err := cmd.Flags().GetString("env")
|
envName, err := cmd.Flags().GetString("env")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,10 +81,23 @@ var runCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldExpandSecrets {
|
if shouldExpandSecrets {
|
||||||
secretsWithSubstitutions := util.SubstituteSecrets(secrets)
|
secrets = util.SubstituteSecrets(secrets)
|
||||||
execCmd(args[0], args[1:], secretsWithSubstitutions)
|
}
|
||||||
|
|
||||||
|
if cmd.Flags().Changed("command") {
|
||||||
|
command := cmd.Flag("command").Value.String()
|
||||||
|
err = executeMultipleCommandWithEnvs(command, secrets)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Something went wrong when executing your command [error=%s]", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
execCmd(args[0], args[1:], secrets)
|
err = executeSingleCommandWithEnvs(args, secrets)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Something went wrong when executing your command [error=%s]", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -68,22 +108,51 @@ func init() {
|
|||||||
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
||||||
runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from")
|
runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from")
|
||||||
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||||
|
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credit: inspired by AWS Valut
|
// Will execute a single command and pass in the given secrets into the process
|
||||||
func execCmd(command string, args []string, envs []models.SingleEnvironmentVariable) error {
|
func executeSingleCommandWithEnvs(args []string, secrets []models.SingleEnvironmentVariable) error {
|
||||||
numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(envs))
|
command := args[0]
|
||||||
|
argsForCommand := args[1:]
|
||||||
|
numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(secrets))
|
||||||
log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected)
|
log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected)
|
||||||
log.Debugf("executing command: %s %s \n", command, strings.Join(args, " "))
|
log.Debugf("executing command: %s %s \n", command, strings.Join(argsForCommand, " "))
|
||||||
log.Debugln("Secrets injected:", envs)
|
log.Debugln("Secrets injected:", secrets)
|
||||||
|
|
||||||
cmd := exec.Command(command, args...)
|
cmd := exec.Command(command, argsForCommand...)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Env = getAllEnvs(envs)
|
cmd.Env = getAllEnvs(secrets)
|
||||||
|
|
||||||
|
return execCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeMultipleCommandWithEnvs(fullCommand string, secrets []models.SingleEnvironmentVariable) error {
|
||||||
|
shell := [2]string{"sh", "-c"}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
shell = [2]string{"cmd", "/C"}
|
||||||
|
} else {
|
||||||
|
shell[0] = os.Getenv("SHELL")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(shell[0], shell[1], fullCommand)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = getAllEnvs(secrets)
|
||||||
|
|
||||||
|
numberOfSecretsInjected := fmt.Sprintf("\u2713 Injected %v Infisical secrets into your application process successfully", len(secrets))
|
||||||
|
log.Infof("\x1b[%dm%s\x1b[0m", 32, numberOfSecretsInjected)
|
||||||
|
log.Debugf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand)
|
||||||
|
log.Debugln("Secrets injected:", secrets)
|
||||||
|
|
||||||
|
return execCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credit: inspired by AWS Valut
|
||||||
|
func execCmd(cmd *exec.Cmd) error {
|
||||||
sigChannel := make(chan os.Signal, 1)
|
sigChannel := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChannel)
|
signal.Notify(sigChannel)
|
||||||
|
|
||||||
@@ -100,7 +169,7 @@ func execCmd(command string, args []string, envs []models.SingleEnvironmentVaria
|
|||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
_ = cmd.Process.Signal(os.Kill)
|
_ = cmd.Process.Signal(os.Kill)
|
||||||
return fmt.Errorf("Failed to wait for command termination: %v", err)
|
return fmt.Errorf("failed to wait for command termination: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)
|
waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ services:
|
|||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- frontend
|
- frontend
|
||||||
- backend
|
- backend
|
||||||
|
|||||||
@@ -30,4 +30,7 @@ infisical export --format=csv > secrets.csv
|
|||||||
|
|
||||||
# Export variables to a JSON file
|
# Export variables to a JSON file
|
||||||
infisical export --format=json > secrets.json
|
infisical export --format=json > secrets.json
|
||||||
|
|
||||||
|
# Export variables to a YAML file
|
||||||
|
infisical export --format=yaml > secrets.yaml
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,9 +2,25 @@
|
|||||||
title: "infisical run"
|
title: "infisical run"
|
||||||
---
|
---
|
||||||
|
|
||||||
```bash
|
<Tabs>
|
||||||
infisical run [options] -- [your application start command]
|
<Tab title="Single command">
|
||||||
```
|
```bash
|
||||||
|
infisical run [options] -- [your application start command]
|
||||||
|
|
||||||
|
# Example
|
||||||
|
infisical run [options] -- npm run dev
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="Chained commands">
|
||||||
|
```bash
|
||||||
|
infisical run [options] --command [string command]
|
||||||
|
|
||||||
|
# Example
|
||||||
|
infisical run [options] --command "npm run bootstrap && npm run dev start; other-bash-command"
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
@@ -15,5 +31,6 @@ Inject environment variables from the platform into an application process.
|
|||||||
| Option | Description | Default value |
|
| Option | Description | Default value |
|
||||||
| -------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
|
| -------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
|
||||||
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
||||||
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` |
|
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | None |
|
||||||
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
|
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
|
||||||
|
| `--command` | Pass secrets into chained commands (e.g., `"first-command && second-command; more-commands..."`) | None |
|
||||||
|
|||||||
@@ -16,59 +16,54 @@ cd infisical
|
|||||||
|
|
||||||
## Set up environment variables
|
## Set up environment variables
|
||||||
|
|
||||||
Before running the docker-compose we have to generate the .env file with the environment variables, you can create your own file or start with the
|
Start by creating a .env file at the root of the Infisical directory
|
||||||
`.env.example` as an example guide.
|
|
||||||
|
|
||||||
Mandatory variables in the `.env` file:
|
<Tip>
|
||||||
|
Reference the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) and provided [`.env.example`](https://raw.githubusercontent.com/Infisical/infisical/main/.env.example) template to fill out your .env file.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
1. Keys and JWT variables
|
### Keys
|
||||||
|
|
||||||

|
`ENCRYPTION_KEY`, `JWT_SIGNUP_SECRET`, `JWT_REFRESH_SECRET`, `JWT_AUTH_SECRET`, `JWT_SERVICE_SECRET` values can be generated with this [32-byte random hex generator](https://www.browserling.com/tools/random-hex).
|
||||||
|
|
||||||
The `.env.example` has these variables empty, you can self generate the `JWT and ENCRYPTION_KEY` with this [32-byte random hex strings generator](https://www.browserling.com/tools/random-hex).
|
### Database
|
||||||
|
|
||||||
For the `PRIVATE_KEY and PUBLIC_KEY` you can use the ones shown in the screenshot:
|
Use to the following `MONGO_URL`, `MONGO_USERNAME`, `MONGO_PASSWORD`, `SITE_URL` values:
|
||||||
|
|
||||||
```
|
```
|
||||||
PRIVATE_KEY='oGVv5rThrpZ7WLgQW27chY1cXngr4wLQIZnGfSKgHPk='
|
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
||||||
PUBLIC_KEY='ldr6JaC7AY+tun3omGLdE4SWpkJbtVBOI54KfUP53Xc='
|
MONGO_USERNAME=root
|
||||||
|
MONGO_PASSWORD=example
|
||||||
|
|
||||||
|
SITE_URL=http://localhost:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Mongo variables and site URL
|
<Info>
|
||||||
|
If you decide to use your own `MONGO_USERNAME` and `MONGO_PASSWORD`, you'll have to modify `MONGO_URL` to take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`.
|
||||||
|
</Info>
|
||||||
|
|
||||||

|
### Mailing
|
||||||
|
|
||||||
These variables are used to connect the MongoDB and set the URL for the localhost.
|
Option 1: Bring your own SMTP server and credentials by filling in `SMTP_HOST`, `SMTP_FROM_ADDRESS`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, and `SMTP_PASSWORD`.
|
||||||
|
<Info>
|
||||||
|
`SMTP_HOST` is set to `smtp.gmail.com` by default. For `SMTP_USERNAME` and `SMTP_PASSWORD`, you'll need an email with 2-step-verification and an [app password](https://support.google.com/mail/answer/185833?hl=en) for it.
|
||||||
|
</Info>
|
||||||
|
|
||||||
For development, you can use `root` for the `MONGO_USERNAME` and `example` for the `MONGO_PASSWORD` as shown in the screenshot.
|
|
||||||
|
|
||||||
Take into account that if you use your own `MONGO_USERNAME` and `MONGO_PASSWORD`, you also have to change the `MONGO_URL` with the form of `MONGO_USERNAME:MONGO_PASSWORD` after the `//` part of the URL.
|
Option 2: Use the provided (Mailhog) SMTP server and browse emails sent by the backend on `http://localhost:8025`. To use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` values:
|
||||||
|
|
||||||
3. Mail SMTP service variables
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following.
|
|
||||||
|
|
||||||
For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification.
|
|
||||||
|
|
||||||
For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable.
|
|
||||||
|
|
||||||
Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
SMTP_HOST=smtp-server
|
SMTP_HOST=smtp-server
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
SMTP_NAME=<whatever you like>
|
SMTP_[email protected]
|
||||||
|
SMTP_FROM_NAME=[whatever you like]
|
||||||
[email protected]
|
[email protected]
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure to leave the `SMTP_PASSWORD` blank so the backend will be able to connect to MailHog
|
<Warning>
|
||||||
|
Make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog.
|
||||||
You can browse `http://localhost:8025/` to browse email messages sent by the backend.
|
</Warning>
|
||||||
|
|
||||||
With these environment variables, you will be ready to run the docker-compose.
|
|
||||||
|
|
||||||
## Docker for development
|
## Docker for development
|
||||||
|
|
||||||
@@ -84,12 +79,4 @@ Then browse http://localhost:8080
|
|||||||
docker-compose -f docker-compose.dev.yml down
|
docker-compose -f docker-compose.dev.yml down
|
||||||
# start services
|
# start services
|
||||||
docker-compose -f docker-compose.dev.yml up
|
docker-compose -f docker-compose.dev.yml up
|
||||||
```
|
```
|
||||||
|
|
||||||
The docker-compose development environment consists of:
|
|
||||||
|
|
||||||
- nginx
|
|
||||||
- frontend
|
|
||||||
- backend
|
|
||||||
- mongo
|
|
||||||
- mongo-express
|
|
||||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 842 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 740 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 862 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "GitHub Actions"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Infisical can sync secrets to GitHub repo secrets only. If your repo uses environment secrets, then stay tuned with this [issue](https://github.com/Infisical/infisical/issues/54).
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Ensure you have admin privileges to the repo you want to sync secrets to.
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Authorize Infisical for GitHub
|
||||||
|
|
||||||
|
Press on the GitHub tile and grant Infisical access to your GitHub account (repo privileges only).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Info>
|
||||||
|
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
|
||||||
|
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Start integration
|
||||||
|
|
||||||
|
Select which Infisical environment secrets you want to sync to which GitHub repo and press start integration to start syncing secrets to the repo.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: "Heroku"
|
title: "Heroku"
|
||||||
description: "With this integration, you can automatically sync your secrets to Heroku as soon as you update secrets in Infisical."
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Instructions
|
Prerequisites:
|
||||||
|
|
||||||
### Step 1: Open the integrations console
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
|
||||||
Open the Infisical Dashboard. Choose the project in which you want to set up the intergation. Go to the integrations tab in the left sidebar.
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
### Step 2: Authenticate with Heroku
|

|
||||||
|
|
||||||
Click on "Heroku" tile. Log in if required and provide the necessary permissions to Infisical. You will afterwards be redirected back to the integrations page.
|
## Authorize Infisical for Heroku
|
||||||
|
|
||||||
Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. With any questions, reach out [email protected].
|
Press on the Heroku tile and grant Infisical access to your Heroku account.
|
||||||
|
|
||||||
### Step 3: Start integration
|

|
||||||
|
|
||||||
Choose a Heroku App that you want to sync the secrets to, and the Infisical project environment that you want to sync the secrets from. Start the integration.
|
<Info>
|
||||||
|
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
|
||||||
The integration should now show status 'In Sync'. Every time you edit secrets, they will be automatically pushed to Heroku.
|
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||||
|
|
||||||
<Info>
|
|
||||||
If you need to update your integration, you will have to delete the current one and create a new one.
|
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
|
## Start integration
|
||||||
|
|
||||||
|
Select which Infisical environment secrets you want to sync to which Heroku app and press start integration to start syncing secrets to Heroku.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: "Netlify"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Infisical integrates with Netlify's new environment variable experience. If your site uses Netlify's old environment variable experience, you'll have to upgrade it to the new one to use this integration.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Authorize Infisical for Netlify
|
||||||
|
|
||||||
|
Press on the Netlify tile and grant Infisical access to your Netlify account.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Info>
|
||||||
|
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
|
||||||
|
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Start integration
|
||||||
|
|
||||||
|
Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press start integration to start syncing secrets to Netlify.
|
||||||
|
|
||||||
|

|
||||||
@@ -2,4 +2,22 @@
|
|||||||
title: "Vercel"
|
title: "Vercel"
|
||||||
---
|
---
|
||||||
|
|
||||||
Coming soon.
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Authorize Infisical for Vercel
|
||||||
|
|
||||||
|
Press on the Vercel tile and grant Infisical access to your Vercel account.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Start integration
|
||||||
|
|
||||||
|
Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press start integration to start syncing secrets to Vercel.
|
||||||
|
|
||||||
|

|
||||||
@@ -12,6 +12,9 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
|
|||||||
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
|
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
|
||||||
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
|
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
|
||||||
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
|
||||||
|
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
|
||||||
|
| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
|
||||||
|
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
|
||||||
| [React](/integrations/frameworks/react) | Framework | Available |
|
| [React](/integrations/frameworks/react) | Framework | Available |
|
||||||
| [Vue](/integrations/frameworks/vue) | Framework | Available |
|
| [Vue](/integrations/frameworks/vue) | Framework | Available |
|
||||||
| [Express](/integrations/frameworks/express) | Framework | Available |
|
| [Express](/integrations/frameworks/express) | Framework | Available |
|
||||||
@@ -26,7 +29,6 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
|
|||||||
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
| [Flask](/integrations/frameworks/flask) | Framework | Available |
|
||||||
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
|
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
|
||||||
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
|
||||||
| [Vercel](/integrations/cloud/vercel) | Cloud | Coming soon |
|
|
||||||
| [Render](/integrations/cloud/render) | Cloud | Coming soon |
|
| [Render](/integrations/cloud/render) | Cloud | Coming soon |
|
||||||
| [Fly.io](/integrations/cloud/flyio) | Cloud | Coming soon |
|
| [Fly.io](/integrations/cloud/flyio) | Cloud | Coming soon |
|
||||||
| AWS | Cloud | Coming soon |
|
| AWS | Cloud | Coming soon |
|
||||||
|
|||||||
@@ -133,13 +133,17 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"integrations/cloud/heroku",
|
"integrations/cloud/heroku",
|
||||||
"integrations/cloud/vercel",
|
"integrations/cloud/vercel",
|
||||||
|
"integrations/cloud/netlify",
|
||||||
"integrations/cloud/render",
|
"integrations/cloud/render",
|
||||||
"integrations/cloud/flyio"
|
"integrations/cloud/flyio"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "CI/CD",
|
"group": "CI/CD",
|
||||||
"pages": ["integrations/cicd/circleci"]
|
"pages": [
|
||||||
|
"integrations/cicd/githubactions",
|
||||||
|
"integrations/cicd/circleci"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Frameworks",
|
"group": "Frameworks",
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ Configuring Infisical requires setting some environment variables. There is a fi
|
|||||||
|
|
||||||
| Variable | Description | Default Value |
|
| Variable | Description | Default Value |
|
||||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- |
|
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- |
|
||||||
| `PRIVATE_KEY` | ❗️ NaCl-generated server secret key | `None` |
|
|
||||||
| `PUBLIC_KEY` | ❗️ NaCl-generated server public key | `None` |
|
|
||||||
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
|
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
|
||||||
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
|
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
|
||||||
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
|
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
|
||||||
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
|
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
|
||||||
|
| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` |
|
||||||
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
|
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
|
||||||
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` |
|
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` |
|
||||||
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
|
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
|
||||||
@@ -24,13 +23,20 @@ Configuring Infisical requires setting some environment variables. There is a fi
|
|||||||
| `MONGO_PASSWORD` | MongoDB password if using container | `None` |
|
| `MONGO_PASSWORD` | MongoDB password if using container | `None` |
|
||||||
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
|
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
|
||||||
| `SMTP_HOST` | Hostname to connect to for establishing SMTP connections | `smtp.gmail.com` |
|
| `SMTP_HOST` | Hostname to connect to for establishing SMTP connections | `smtp.gmail.com` |
|
||||||
| `SMTP_NAME` | Name label to be used in From field (e.g. `Team`) | `None` |
|
| `SMTP_SECURE` | Use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
|
||||||
|
| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
|
||||||
|
| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `[email protected]`) | `None` |
|
||||||
|
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
|
||||||
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `[email protected]`) | `None` |
|
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `[email protected]`) | `None` |
|
||||||
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
||||||
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
|
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
|
||||||
| `CLIENT_ID_VERCEL` | OAuth client id for Vercel integration | `None` |
|
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
|
||||||
| `CLIENT_ID_NETLIFY` | OAuth client id for Netlify integration | `None` |
|
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
|
||||||
| `CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` |
|
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
|
||||||
| `CLIENT_SECRET_VERCEL` | OAuth client secret for Vercel integration | `None` |
|
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
|
||||||
| `CLIENT_SECRET_NETLIFY` | OAuth client secret for Netlify integration | `None` |
|
| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` |
|
||||||
|
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` |
|
||||||
|
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` |
|
||||||
|
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` |
|
||||||
|
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` |
|
||||||
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
|
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ that by adding the `--namespace <namespace-to-install-to>` to your `helm install
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
## Installs to default namespace
|
## Installs to default namespace
|
||||||
helm install infisical-helm-charts/infisical --values <path to the values.yaml you downloaded/created in step 2>
|
helm install infisical-helm-charts/infisical --generate-name --values <path to the values.yaml you downloaded/created in step 2>
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
@@ -50,5 +50,4 @@ If you have not filled out all of the required environment variables, you will s
|
|||||||
do so.
|
do so.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
#### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
||||||
Note: Please allow an additional time (2 minutes) for the frontend pods to be fully ready.
|
|
||||||
@@ -11,7 +11,7 @@ import { Listbox, Transition } from "@headlessui/react";
|
|||||||
interface ListBoxProps {
|
interface ListBoxProps {
|
||||||
selected: string;
|
selected: string;
|
||||||
onChange: (arg: string) => void;
|
onChange: (arg: string) => void;
|
||||||
data: string[];
|
data: string[] | null;
|
||||||
text?: string;
|
text?: string;
|
||||||
buttonAction?: () => void;
|
buttonAction?: () => void;
|
||||||
isFull?: boolean;
|
isFull?: boolean;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
|
|
||||||
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
|
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
|
||||||
|
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
|
|
||||||
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
|
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
|
||||||
|
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import deleteIntegration from "../../pages/api/integrations/DeleteIntegration"
|
|||||||
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
|
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
|
||||||
import updateIntegration from "../../pages/api/integrations/updateIntegration"
|
import updateIntegration from "../../pages/api/integrations/updateIntegration"
|
||||||
import {
|
import {
|
||||||
|
contextNetlifyMapping,
|
||||||
envMapping,
|
envMapping,
|
||||||
reverseContextNetlifyMapping,
|
reverseContextNetlifyMapping,
|
||||||
reverseEnvMapping} from "../../public/data/frequentConstants";
|
reverseEnvMapping,
|
||||||
|
} from "../../public/data/frequentConstants";
|
||||||
|
|
||||||
interface Integration {
|
interface Integration {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -25,6 +27,7 @@ interface Integration {
|
|||||||
integration: string;
|
integration: string;
|
||||||
integrationAuth: string;
|
integrationAuth: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
context: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IntegrationApp {
|
interface IntegrationApp {
|
||||||
@@ -69,7 +72,7 @@ const Integration = ({
|
|||||||
setIntegrationTarget("Development");
|
setIntegrationTarget("Development");
|
||||||
break;
|
break;
|
||||||
case "netlify":
|
case "netlify":
|
||||||
setIntegrationContext("All");
|
setIntegrationContext(integration?.context ? contextNetlifyMapping[integration.context] : "Local development");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -93,7 +96,7 @@ const Integration = ({
|
|||||||
"Production",
|
"Production",
|
||||||
"Preview",
|
"Preview",
|
||||||
"Development"
|
"Development"
|
||||||
] : []}
|
] : null}
|
||||||
selected={"Production"}
|
selected={"Production"}
|
||||||
onChange={setIntegrationTarget}
|
onChange={setIntegrationTarget}
|
||||||
/>
|
/>
|
||||||
@@ -107,12 +110,11 @@ const Integration = ({
|
|||||||
</div>
|
</div>
|
||||||
<ListBox
|
<ListBox
|
||||||
data={!integration.isActive ? [
|
data={!integration.isActive ? [
|
||||||
"All",
|
|
||||||
"Production",
|
"Production",
|
||||||
"Deploy previews",
|
"Deploy previews",
|
||||||
"Branch deploys",
|
"Branch deploys",
|
||||||
"Local development"
|
"Local development"
|
||||||
] : []}
|
] : null}
|
||||||
selected={integrationContext}
|
selected={integrationContext}
|
||||||
onChange={setIntegrationContext}
|
onChange={setIntegrationContext}
|
||||||
/>
|
/>
|
||||||
@@ -138,7 +140,7 @@ const Integration = ({
|
|||||||
"Staging",
|
"Staging",
|
||||||
"Testing",
|
"Testing",
|
||||||
"Production",
|
"Production",
|
||||||
] : []}
|
] : null}
|
||||||
selected={integrationEnvironment}
|
selected={integrationEnvironment}
|
||||||
onChange={(environment) => {
|
onChange={(environment) => {
|
||||||
setIntegrationEnvironment(environment);
|
setIntegrationEnvironment(environment);
|
||||||
@@ -166,7 +168,7 @@ const Integration = ({
|
|||||||
APP
|
APP
|
||||||
</div>
|
</div>
|
||||||
<ListBox
|
<ListBox
|
||||||
data={!integration.isActive ? apps.map((app) => app.name) : []}
|
data={!integration.isActive ? apps.map((app) => app.name) : null}
|
||||||
selected={integrationApp}
|
selected={integrationApp}
|
||||||
onChange={(app) => {
|
onChange={(app) => {
|
||||||
setIntegrationApp(app);
|
setIntegrationApp(app);
|
||||||
@@ -190,7 +192,8 @@ const Integration = ({
|
|||||||
onButtonPressed={async () => {
|
onButtonPressed={async () => {
|
||||||
|
|
||||||
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
||||||
const siteId = siteApp ? siteApp.siteId : null;
|
const siteId = siteApp?.siteId ? siteApp.siteId : null;
|
||||||
|
|
||||||
const result = await updateIntegration({
|
const result = await updateIntegration({
|
||||||
integrationId: integration._id,
|
integrationId: integration._id,
|
||||||
environment: envMapping[integrationEnvironment],
|
environment: envMapping[integrationEnvironment],
|
||||||
@@ -200,6 +203,7 @@ const Integration = ({
|
|||||||
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
|
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
|
||||||
siteId
|
siteId
|
||||||
});
|
});
|
||||||
|
|
||||||
router.reload();
|
router.reload();
|
||||||
}}
|
}}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface IntegrationType {
|
|||||||
integration: string;
|
integration: string;
|
||||||
integrationAuth: string;
|
integrationAuth: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
context: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectIntegrationSection = ({
|
const ProjectIntegrationSection = ({
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ const attemptLogin = async (
|
|||||||
async () => {
|
async () => {
|
||||||
const clientPublicKey = client.getPublicKey();
|
const clientPublicKey = client.getPublicKey();
|
||||||
|
|
||||||
const { serverPublicKey, salt } = await login1(email, clientPublicKey);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const { serverPublicKey, salt } = await login1(email, clientPublicKey);
|
||||||
|
|
||||||
client.setSalt(salt);
|
client.setSalt(salt);
|
||||||
client.setServerPublicKey(serverPublicKey);
|
client.setServerPublicKey(serverPublicKey);
|
||||||
const clientProof = client.getProof(); // called M1
|
const clientProof = client.getProof(); // called M1
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
const queryString = require("query-string");
|
||||||
|
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||||
|
|
||||||
|
export default function Github() {
|
||||||
|
const router = useRouter();
|
||||||
|
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||||
|
const code = parsedUrl.code;
|
||||||
|
const state = parsedUrl.state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here we forward to the default workspace if a user opens this url
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
useEffect(async () => {
|
||||||
|
try {
|
||||||
|
if (state === localStorage.getItem('latestCSRFToken')) {
|
||||||
|
localStorage.removeItem('latestCSRFToken');
|
||||||
|
await AuthorizeIntegration({
|
||||||
|
workspaceId: localStorage.getItem('projectData.id'),
|
||||||
|
code,
|
||||||
|
integration: "github",
|
||||||
|
});
|
||||||
|
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Github integration error: ', error);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Github.requireAuth = true;
|
||||||
@@ -41,7 +41,7 @@ export default function Integrations() {
|
|||||||
setCloudIntegrationOptions(
|
setCloudIntegrationOptions(
|
||||||
await getIntegrationOptions()
|
await getIntegrationOptions()
|
||||||
);
|
);
|
||||||
|
|
||||||
// get project integration authorizations
|
// get project integration authorizations
|
||||||
setIntegrationAuths(
|
setIntegrationAuths(
|
||||||
await getWorkspaceAuthorizations({
|
await getWorkspaceAuthorizations({
|
||||||
@@ -123,6 +123,8 @@ export default function Integrations() {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const handleIntegrationOption = async ({ integrationOption }) => {
|
const handleIntegrationOption = async ({ integrationOption }) => {
|
||||||
|
|
||||||
|
console.log('handleIntegrationOption', integrationOption);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||||
@@ -134,11 +136,14 @@ export default function Integrations() {
|
|||||||
window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
|
window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
|
||||||
break;
|
break;
|
||||||
case 'Vercel':
|
case 'Vercel':
|
||||||
window.location = `https://vercel.com/integrations/infisical-dev/new?state=${state}`;
|
window.location = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
|
||||||
break;
|
break;
|
||||||
case 'Netlify':
|
case 'Netlify':
|
||||||
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
|
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
|
||||||
break;
|
break;
|
||||||
|
case 'GitHub':
|
||||||
|
window.location = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/github&state=${state}`;
|
||||||
|
break;
|
||||||
// case 'Fly.io':
|
// case 'Fly.io':
|
||||||
// console.log('fly.io');
|
// console.log('fly.io');
|
||||||
// setIntegrationAccessTokenDialogOpen(true);
|
// setIntegrationAccessTokenDialogOpen(true);
|
||||||
|
|||||||
@@ -16,8 +16,14 @@ const reverseEnvMapping: Mapping = {
|
|||||||
test: "Testing",
|
test: "Testing",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contextNetlifyMapping: Mapping = {
|
||||||
|
"dev": "Local development",
|
||||||
|
"branch-deploy": "Branch deploys",
|
||||||
|
"deploy-review": "Deploy Previews",
|
||||||
|
"production": "Production"
|
||||||
|
}
|
||||||
|
|
||||||
const reverseContextNetlifyMapping: Mapping = {
|
const reverseContextNetlifyMapping: Mapping = {
|
||||||
"All": "all",
|
|
||||||
"Local development": "dev",
|
"Local development": "dev",
|
||||||
"Branch deploys": "branch-deploy",
|
"Branch deploys": "branch-deploy",
|
||||||
"Deploy Previews": "deploy-preview",
|
"Deploy Previews": "deploy-preview",
|
||||||
@@ -25,6 +31,7 @@ const reverseContextNetlifyMapping: Mapping = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
contextNetlifyMapping,
|
||||||
envMapping,
|
envMapping,
|
||||||
reverseContextNetlifyMapping,
|
reverseContextNetlifyMapping,
|
||||||
reverseEnvMapping};
|
reverseEnvMapping}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 8.4 KiB |
@@ -7,7 +7,7 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.1.3
|
version: 0.1.6
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ spec:
|
|||||||
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 4000
|
- containerPort: 4000
|
||||||
|
{{- if .Values.backend.kubeSecretRef }}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: {{ .Values.backend.kubeSecretRef }}
|
||||||
|
{{- end }}
|
||||||
env:
|
env:
|
||||||
{{- range $key, $value := .Values.backendEnvironmentVariables }}
|
{{- range $key, $value := .Values.backendEnvironmentVariables }}
|
||||||
{{- if $value | quote | eq "MUST_REPLACE" }}
|
{{- if $value | quote | eq "MUST_REPLACE" }}
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ spec:
|
|||||||
- name: frontend
|
- name: frontend
|
||||||
image: infisical/frontend
|
image: infisical/frontend
|
||||||
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
|
||||||
|
{{- if .Values.frontend.kubeSecretRef }}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: {{ .Values.frontend.kubeSecretRef }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.frontendEnvironmentVariables }}
|
||||||
env:
|
env:
|
||||||
{{- range $key, $value := .Values.frontendEnvironmentVariables }}
|
{{- range $key, $value := .Values.frontendEnvironmentVariables }}
|
||||||
{{- if $value | quote | eq "MUST_REPLACE" }}
|
{{- if $value | quote | eq "MUST_REPLACE" }}
|
||||||
@@ -26,8 +32,9 @@ spec:
|
|||||||
- name: {{ $key }}
|
- name: {{ $key }}
|
||||||
value: {{ quote $value }}
|
value: {{ quote $value }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 4000
|
- containerPort: 3000
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
# PLEASE REPLACE VALUES/EDIT AS REQUIRED
|
# PLEASE REPLACE VALUES/EDIT AS REQUIRED
|
||||||
#####
|
#####
|
||||||
|
|
||||||
namespace: infisical
|
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
image:
|
image:
|
||||||
repository:
|
repository:
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
tag: "latest"
|
tag: "latest"
|
||||||
|
# kubeSecretRef: some-kube-secret-name
|
||||||
|
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
@@ -18,10 +18,12 @@ backend:
|
|||||||
repository:
|
repository:
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
tag: "latest"
|
tag: "latest"
|
||||||
|
# kubeSecretRef: some-kube-secret-name
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
annotations: {}
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: "nginx"
|
||||||
hostName: example.com
|
hostName: example.com
|
||||||
frontend:
|
frontend:
|
||||||
path: /
|
path: /
|
||||||
@@ -54,8 +56,6 @@ ingress:
|
|||||||
###
|
###
|
||||||
backendEnvironmentVariables:
|
backendEnvironmentVariables:
|
||||||
# Required keys for platform encryption/decryption ops. Replace with nacl sk keys
|
# Required keys for platform encryption/decryption ops. Replace with nacl sk keys
|
||||||
PRIVATE_KEY: MUST_REPLACE
|
|
||||||
PUBLIC_KEY: MUST_REPLACE
|
|
||||||
ENCRYPTION_KEY: MUST_REPLACE
|
ENCRYPTION_KEY: MUST_REPLACE
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
@@ -71,9 +71,8 @@ backendEnvironmentVariables:
|
|||||||
SMTP_USERNAME: MUST_REPLACE
|
SMTP_USERNAME: MUST_REPLACE
|
||||||
SMTP_PASSWORD: MUST_REPLACE
|
SMTP_PASSWORD: MUST_REPLACE
|
||||||
|
|
||||||
# You may replace with Mongo Cloud URI
|
# Recommended to replace with Mongo Cloud URI as the DB instance in the cluster does not have persistence yet
|
||||||
MONGO_URL: mongodb://root:root@mongodb-service:27017/
|
MONGO_URL: mongodb://root:root@mongodb-service:27017/
|
||||||
|
|
||||||
# frontendEnvironmentVariables:
|
# frontendEnvironmentVariables:
|
||||||
# INFISICAL_TELEMETRY_ENABLED: true
|
|
||||||
|
|
||||||