This commit is contained in:
Vladyslav Matsiiako
2022-12-22 14:31:29 -05:00
71 changed files with 2606 additions and 1333 deletions
+11 -8
View File
@@ -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=
+41
View File
@@ -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 }}
+22 -21
View File
@@ -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 }}
+3 -1
View File
@@ -25,7 +25,9 @@ node_modules
.env .env
# testing # testing
/coverage coverage
reports
junit.xml
# next.js # next.js
/.next/ /.next/
+1
View File
@@ -81,6 +81,7 @@ nfpms:
- rpm - rpm
- deb - deb
- apk - apk
- archlinux
bindir: /usr/bin bindir: /usr/bin
scoop: scoop:
bucket: bucket:
+13 -7
View File
@@ -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>
+19
View File
@@ -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);
});
});
-2
View File
@@ -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;
+878 -251
View File
File diff suppressed because it is too large Load Diff
+37 -3
View File
@@ -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}"
} }
} }
+74
View File
@@ -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]}`);
});
+12 -6
View File
@@ -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,
-13
View File
@@ -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';
/** /**
@@ -85,15 +84,3 @@ 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
});
};
+1 -1
View File
@@ -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({
+3 -3
View File
@@ -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
}); });
+9 -35
View File
@@ -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 };
+13 -117
View File
@@ -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);
setUpHealthEndpoint(server);
setTransporter(initSmtp());
if (NODE_ENV !== 'test') {
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
debug: NODE_ENV === 'production' ? false : true, debug: NODE_ENV === 'production' ? false : true,
environment: NODE_ENV environment: NODE_ENV
}); });
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';
const connectWithRetry = () => {
mongoose
.connect(MONGO_URL)
.then(() => console.log('Successfully connected to DB'))
.catch((e) => {
console.log('Failed to connect to DB ', e);
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);
});
+73 -29
View File
@@ -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 { import { Octokit } from '@octokit/rest';
IIntegrationAuth import { IIntegrationAuth } from '../models';
} from '../models';
import { import {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_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
@@ -27,7 +32,6 @@ const getApps = async ({
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
}) => { }) => {
interface App { interface App {
name: string; name: string;
siteId?: string; siteId?: string;
@@ -52,8 +56,13 @@ const getApps = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_GITHUB:
apps = await getAppsGithub({
integrationAuth,
accessToken
});
break;
} }
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
@@ -61,7 +70,7 @@ const getApps = async ({
} }
return apps; return apps;
} };
/** /**
* Return list of names of apps for Heroku integration * Return list of names of apps for Heroku integration
@@ -70,19 +79,17 @@ 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
}: {
accessToken: string;
}) => {
let apps; let apps;
try { try {
const res = (await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, { const res = (
await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: { headers: {
Accept: 'application/vnd.heroku+json; version=3', Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
})).data; })
).data;
apps = res.map((a: any) => ({ apps = res.map((a: any) => ({
name: a.name name: a.name
@@ -94,7 +101,7 @@ const getAppsHeroku = async ({
} }
return apps; return apps;
} };
/** /**
* Return list of names of apps for Vercel integration * Return list of names of apps for Vercel integration
@@ -103,18 +110,16 @@ 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
}: {
accessToken: string;
}) => {
let apps; let apps;
try { try {
const res = (await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, { const res = (
await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
})).data; })
).data;
apps = res.projects.map((a: any) => ({ apps = res.projects.map((a: any) => ({
name: a.name name: a.name
@@ -126,7 +131,7 @@ const getAppsVercel = async ({
} }
return apps; return apps;
} };
/** /**
* Return list of names of sites for Netlify integration * Return list of names of sites for Netlify integration
@@ -144,17 +149,18 @@ const getAppsNetlify = async ({
}) => { }) => {
let apps; let apps;
try { try {
const res = (await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, { const res = (
await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
})).data; })
).data;
apps = res.map((a: any) => ({ apps = res.map((a: any) => ({
name: a.name, name: a.name,
siteId: a.site_id siteId: a.site_id
})); }));
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
@@ -162,8 +168,46 @@ const getAppsNetlify = async ({
} }
return 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');
} }
export { return apps;
getApps };
}
export { getApps };
+83 -37
View File
@@ -4,19 +4,25 @@ import {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITHUB_API_URL,
ACTION_PUSH_TO_HEROKU 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_HEROKU,
CLIENT_SECRET_VERCEL, CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB
} from '../config'; } from '../config';
import { user } from '../routes';
interface ExchangeCodeHerokuResponse { interface ExchangeCodeHerokuResponse {
token_type: string; token_type: string;
@@ -43,6 +49,12 @@ interface ExchangeCodeNetlifyResponse {
created_at: number; created_at: number;
} }
interface ExchangeCodeGithubResponse {
access_token: string;
scope: string;
token_type: string;
}
/** /**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2 * Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
* code-token exchange for integration named [integration] * code-token exchange for integration named [integration]
@@ -81,6 +93,11 @@ const exchangeCode = async ({
code code
}); });
break; break;
case INTEGRATION_GITHUB:
obj = await exchangeCodeGithub({
code
});
break;
} }
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -89,7 +106,7 @@ const exchangeCode = async ({
} }
return obj; 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,14 +161,11 @@ 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
}: {
code: string;
}) => {
let res: ExchangeCodeVercelResponse; let res: ExchangeCodeVercelResponse;
try { try {
res = (await axios.post( res = (
await axios.post(
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
code: code, code: code,
@@ -159,20 +173,21 @@ const exchangeCodeVercel = async ({
client_secret: CLIENT_SECRET_VERCEL, client_secret: CLIENT_SECRET_VERCEL,
redirect_uri: `${SITE_URL}/vercel` redirect_uri: `${SITE_URL}/vercel`
} as any) } as any)
)).data; )
).data;
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Vercel'); throw new Error('Failed OAuth2 code-token exchange with Vercel');
} }
return ({ return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: null, refreshToken: null,
accessExpiresAt: null, accessExpiresAt: null,
teamId: res.team_id teamId: res.team_id
}); };
} };
/** /**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel * Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
@@ -184,15 +199,12 @@ 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
}: {
code: string;
}) => {
let res: ExchangeCodeNetlifyResponse; let res: ExchangeCodeNetlifyResponse;
let accountId; let accountId;
try { try {
res = (await axios.post( res = (
await axios.post(
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
@@ -201,41 +213,75 @@ const exchangeCodeNetlify = async ({
client_secret: CLIENT_SECRET_NETLIFY, client_secret: CLIENT_SECRET_NETLIFY,
redirect_uri: `${SITE_URL}/netlify` redirect_uri: `${SITE_URL}/netlify`
} as any) } as any)
)).data; )
).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: { headers: {
Authorization: `Bearer ${res.access_token}` Authorization: `Bearer ${res.access_token}`
} }
} });
);
const res3 = (await axios.get( const res3 = (
'https://api.netlify.com/api/v1/accounts', await axios.get('https://api.netlify.com/api/v1/accounts', {
{
headers: { headers: {
Authorization: `Bearer ${res.access_token}` Authorization: `Bearer ${res.access_token}`
} }
} })
)).data; ).data;
accountId = res3[0].id; accountId = res3[0].id;
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Netlify'); throw new Error('Failed OAuth2 code-token exchange with Netlify');
} }
return ({ return {
accessToken: res.access_token, accessToken: res.access_token,
refreshToken: res.refresh_token, refreshToken: res.refresh_token,
accountId 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');
} }
export { return {
exchangeCode accessToken: res.access_token,
} refreshToken: null,
accessExpiresAt: null
};
};
export { exchangeCode };
+3 -5
View File
@@ -38,7 +38,7 @@ const exchangeRefresh = async ({
} }
return accessToken; return accessToken;
} };
/** /**
* Return new access token by exchanging refresh token [refreshToken] for the * Return new access token by exchanging refresh token [refreshToken] for the
@@ -71,8 +71,6 @@ const exchangeRefreshHeroku = async ({
} }
return accessToken; return accessToken;
} };
export { export { exchangeRefresh };
exchangeRefresh
}
+9 -12
View File
@@ -1,22 +1,19 @@
import axios from 'axios'; import axios from 'axios';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { import { IIntegrationAuth, IntegrationAuth, Integration } from '../models';
IIntegrationAuth,
IntegrationAuth,
Integration
} from '../models';
import { import {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
} 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
@@ -27,6 +24,8 @@ const revokeAccess = async ({
break; break;
case INTEGRATION_NETLIFY: case INTEGRATION_NETLIFY:
break; break;
case INTEGRATION_GITHUB:
break;
} }
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({ const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
@@ -43,8 +42,6 @@ const revokeAccess = async ({
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to delete integration authorization'); throw new Error('Failed to delete integration authorization');
} }
} };
export { export { revokeAccess };
revokeAccess
}
+241 -65
View File
@@ -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 { import { Octokit } from '@octokit/rest';
IIntegration, IIntegrationAuth // import * as sodium from 'libsodium-wrappers';
} from '../models'; import sodium from 'libsodium-wrappers';
// const sodium = require('libsodium-wrappers');
import { IIntegration, IIntegrationAuth } from '../models';
import { import {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_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)
@@ -29,7 +34,7 @@ const syncSecrets = async ({
integration, integration,
integrationAuth, integrationAuth,
secrets, secrets,
accessToken, accessToken
}: { }: {
integration: IIntegration; integration: IIntegration;
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
@@ -60,13 +65,20 @@ const syncSecrets = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_GITHUB:
await syncSecretsGitHub({
integration,
secrets,
accessToken
});
break;
} }
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to sync secrets to integration'); throw new Error('Failed to sync secrets to integration');
} }
} };
/** /**
* Sync/push [secrets] to Heroku [app] * Sync/push [secrets] to Heroku [app]
@@ -79,12 +91,13 @@ const syncSecretsHeroku = async ({
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 = (
await axios.get(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
{ {
headers: { headers: {
@@ -92,9 +105,10 @@ const syncSecretsHeroku = async ({
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
} }
)).data; )
).data;
Object.keys(herokuSecrets).forEach(key => { Object.keys(herokuSecrets).forEach((key) => {
if (!(key in secrets)) { if (!(key in secrets)) {
secrets[key] = null; secrets[key] = null;
} }
@@ -115,7 +129,7 @@ const syncSecretsHeroku = async ({
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to sync secrets to Heroku'); 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,62 +334,86 @@ 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 { // identify secrets to create and update
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
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
.reduce((obj: any, value: NetlifyValue) => ({
...obj,
[value.context]: value
}), {});
// Identify secrets to update and delete if (integration.context in contexts) {
Object.keys(res).map((key) => { // case: Netlify secret value exists in integration context
if (key in secrets) { if (secrets[key] !== contexts[integration.context].value) {
if (res[key] !== secrets[key]) { // case: Infisical and Netlify secret values are different
// case: secret value has changed // -> update Netlify secret context and value
updateSecrets.push({ updateSecrets.push({
key: key, key,
values: [{
context: integration.context, context: integration.context,
value: secrets[key] value: secrets[key]
}]
}); });
} }
} else { } else {
// case: secret has been deleted // case: Netlify secret value does not exist in integration context
deleteSecrets.push({ // -> add the new Netlify secret context and value
key updateSecrets.push({
key,
values: [{
context: integration.context,
value: secrets[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
}]
});
}
}
}); });
} }
}); });
@@ -368,7 +422,6 @@ const syncSecretsNetlify = async ({
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,10 @@ 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, params: syncParams,
headers: { headers: {
@@ -417,6 +467,19 @@ const syncSecretsNetlify = async ({
}); });
} }
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,
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
});
}
} 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 };
+13 -7
View File
@@ -6,7 +6,8 @@ import {
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 {
@@ -18,7 +19,7 @@ export interface IIntegration {
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;
} }
@@ -38,19 +39,23 @@ const integrationSchema = new Schema<IIntegration>(
type: Boolean, type: Boolean,
required: true required: true
}, },
app: { // name of app in provider app: {
// name of app in provider
type: String, type: String,
default: null default: null
}, },
target: { // vercel-specific target (environment) target: {
// vercel-specific target (environment)
type: String, type: String,
default: null default: null
}, },
context: { // netlify-specific context (deploy) context: {
// netlify-specific context (deploy)
type: String, type: String,
default: null default: null
}, },
siteId: { // netlify-specific site (app) id siteId: {
// netlify-specific site (app) id
type: String, type: String,
default: null default: null
}, },
@@ -59,7 +64,8 @@ const integrationSchema = new Schema<IIntegration>(
enum: [ enum: [
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
], ],
required: true required: true
}, },
+9 -5
View File
@@ -2,13 +2,14 @@ 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;
@@ -31,14 +32,17 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
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 teamId: {
// vercel-specific integration param
type: String type: String
}, },
accountId: { // netlify-specific integration param accountId: {
// netlify-specific integration param
type: String type: String
}, },
refreshCiphertext: { refreshCiphertext: {
-2
View File
@@ -34,6 +34,4 @@ router.get(
keyController.getLatestKey keyController.getLatestKey
); );
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
export default router; export default router;
+10
View File
@@ -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;
};
+32
View File
@@ -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
}
});
};
+34
View File
@@ -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;
};
+9
View File
@@ -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');
} }
+10 -15
View File
@@ -9,14 +9,17 @@ import {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_SET, INTEGRATION_SET,
INTEGRATION_OAUTH2, INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL, INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL,
INTEGRATION_OPTIONS INTEGRATION_OPTIONS
} from './integration'; } from './integration';
import { import {
@@ -28,21 +31,10 @@ import {
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,
@@ -64,14 +56,17 @@ export {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_SET, INTEGRATION_SET,
INTEGRATION_OAUTH2, INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL, INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL,
EVENT_PUSH_SECRETS, EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS, EVENT_PULL_SECRETS,
ACTION_PUSH_TO_HEROKU, ACTION_PUSH_TO_HEROKU,
+26 -4
View File
@@ -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',
@@ -105,13 +124,16 @@ export {
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_SET, INTEGRATION_SET,
INTEGRATION_OAUTH2, INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL, INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL,
INTEGRATION_OPTIONS 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
+5
View File
@@ -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';
+3 -6
View File
@@ -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"
]
} }
+83 -14
View File
@@ -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)
+1 -1
View File
@@ -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
+3
View File
@@ -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
``` ```
+18 -1
View File
@@ -2,9 +2,25 @@
title: "infisical run" title: "infisical run"
--- ---
<Tabs>
<Tab title="Single command">
```bash ```bash
infisical run [options] -- [your application start command] 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 |
+27 -40
View File
@@ -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
![image](https://user-images.githubusercontent.com/118568289/206791534-9c9d1431-e83d-49c0-8a54-b373ed0df820.png) `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>
![image](https://user-images.githubusercontent.com/118568289/206792171-3376e3c6-c3ac-4d5d-8776-d78ee089b520.png) ### 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
![image](https://user-images.githubusercontent.com/118568289/206792653-ba3211d1-1071-43f2-93a7-8b408bbd9e0e.png)
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
@@ -85,11 +80,3 @@ 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
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+34
View File
@@ -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
![integrations](../../images/integrations.png)
## Authorize Infisical for GitHub
Press on the GitHub tile and grant Infisical access to your GitHub account (repo privileges only).
![integrations github authorization](../../images/integrations-github-auth.png)
<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.
![integrations github](../../images/integrations-github.png)
+16 -13
View File
@@ -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 ![integrations](../../images/integrations.png)
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 ![integrations heroku authorization](../../images/integrations-heroku-auth.png)
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.
The integration should now show status 'In Sync'. Every time you edit secrets, they will be automatically pushed to Heroku.
<Info> <Info>
If you need to update your integration, you will have to delete the current one and create a new one. 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> </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.
![integrations heroku](../../images/integrations-heroku.png)
+32
View File
@@ -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
![integrations](../../images/integrations.png)
## Authorize Infisical for Netlify
Press on the Netlify tile and grant Infisical access to your Netlify account.
![integrations netlify authorization](../../images/integrations-netlify-auth.png)
<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.
![integrations netlify](../../images/integrations-netlify.png)
+19 -1
View File
@@ -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
![integrations](../../images/integrations.png)
## Authorize Infisical for Vercel
Press on the Vercel tile and grant Infisical access to your Vercel account.
![integrations vercel authorization](../../images/integrations-vercel-auth.png)
## 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.
![integrations vercel](../../images/integrations-vercel.png)
+3 -1
View File
@@ -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 |
+5 -1
View File
@@ -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",
+14 -8
View File
@@ -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` |
+2 -3
View File
@@ -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.
+1 -1
View File
@@ -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();
try {
const { serverPublicKey, salt } = await login1(email, clientPublicKey); const { serverPublicKey, salt } = await login1(email, clientPublicKey);
try {
client.setSalt(salt); client.setSalt(salt);
client.setServerPublicKey(serverPublicKey); client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1 const clientProof = client.getProof(); // called M1
+37
View File
@@ -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;
+6 -1
View File
@@ -124,6 +124,8 @@ export default function Integrations() {
*/ */
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
const state = crypto.randomBytes(16).toString("hex"); const state = crypto.randomBytes(16).toString("hex");
@@ -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);
+9 -2
View File
@@ -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}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

+1 -1
View File
@@ -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
+6 -7
View File
@@ -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