Merge branch 'main' into feat/error-handling
30
.env.example
@@ -1,15 +1,13 @@
|
|||||||
# Keys
|
# Keys
|
||||||
# Required keys for platform encryption/decryption ops
|
# Required key for platform encryption/decryption ops
|
||||||
PRIVATE_KEY=replace_with_nacl_sk
|
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
||||||
PUBLIC_KEY=replace_with_nacl_pk
|
|
||||||
ENCRYPTION_KEY=replace_with_lengthy_secure_hex
|
|
||||||
|
|
||||||
# JWT
|
# JWT
|
||||||
# Required secrets to sign JWT tokens
|
# Required secrets to sign JWT tokens
|
||||||
JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex
|
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
|
||||||
JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex
|
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
|
||||||
JWT_AUTH_SECRET=replace_with_lengthy_secure_hex
|
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
|
||||||
JWT_SERVICE_SECRET=replace_with_lengthy_secure_hex
|
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
|
||||||
|
|
||||||
# JWT lifetime
|
# JWT lifetime
|
||||||
# Optional lifetimes for JWT tokens expressed in seconds or a string
|
# Optional lifetimes for JWT tokens expressed in seconds or a string
|
||||||
@@ -33,26 +31,28 @@ MONGO_PASSWORD=example
|
|||||||
|
|
||||||
# Website URL
|
# Website URL
|
||||||
# Required
|
# Required
|
||||||
|
|
||||||
SITE_URL=http://localhost:8080
|
SITE_URL=http://localhost:8080
|
||||||
|
|
||||||
# Mail/SMTP
|
# Mail/SMTP
|
||||||
# Required to send emails
|
SMTP_HOST= # required
|
||||||
# By default, SMTP_HOST is set to smtp.gmail.com
|
SMTP_USERNAME= # required
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_PASSWORD= # required
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_NAME=Team
|
SMTP_SECURE=false
|
||||||
SMTP_USERNAME=team@infisical.com
|
SMTP_FROM_ADDRESS= # required
|
||||||
SMTP_PASSWORD=
|
SMTP_FROM_NAME=Infisical
|
||||||
|
|
||||||
# Integration
|
# Integration
|
||||||
# Optional only if integration is used
|
# Optional only if integration is used
|
||||||
CLIENT_ID_HEROKU=
|
CLIENT_ID_HEROKU=
|
||||||
CLIENT_ID_VERCEL=
|
CLIENT_ID_VERCEL=
|
||||||
CLIENT_ID_NETLIFY=
|
CLIENT_ID_NETLIFY=
|
||||||
|
CLIENT_ID_GITHUB=
|
||||||
CLIENT_SECRET_HEROKU=
|
CLIENT_SECRET_HEROKU=
|
||||||
CLIENT_SECRET_VERCEL=
|
CLIENT_SECRET_VERCEL=
|
||||||
CLIENT_SECRET_NETLIFY=
|
CLIENT_SECRET_NETLIFY=
|
||||||
|
CLIENT_SECRET_GITHUB=
|
||||||
|
CLIENT_SLUG_VERCEL=
|
||||||
|
|
||||||
# Sentry (optional) for monitoring errors
|
# Sentry (optional) for monitoring errors
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
|
|||||||
22
.github/workflows/close_inactive_issues.yml
vendored
@@ -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 }}
|
|
||||||
10
README.md
@@ -146,7 +146,9 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
🔜 AWS
|
🔜 AWS
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 GitHub Actions (https://github.com/Infisical/infisical/issues/54)
|
<a href="https://infisical.com/docs/integrations/cicd/githubactions">
|
||||||
|
✔️ GitHub Actions
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Railway
|
🔜 Railway
|
||||||
@@ -179,7 +181,9 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
🔜 TravisCI
|
🔜 TravisCI
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Netlify (https://github.com/Infisical/infisical/issues/55)
|
<a href="https://infisical.com/docs/integrations/cloud/netlify">
|
||||||
|
✔️ Netlify
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
🔜 Railway
|
🔜 Railway
|
||||||
@@ -317,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
|
|
||||||
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
||||||
|
|||||||
2
backend/environment.d.ts
vendored
@@ -24,8 +24,6 @@ declare global {
|
|||||||
CLIENT_SECRET_NETLIFY: string;
|
CLIENT_SECRET_NETLIFY: string;
|
||||||
POSTHOG_HOST: string;
|
POSTHOG_HOST: string;
|
||||||
POSTHOG_PROJECT_API_KEY: string;
|
POSTHOG_PROJECT_API_KEY: string;
|
||||||
PRIVATE_KEY: string;
|
|
||||||
PUBLIC_KEY: string;
|
|
||||||
SENTRY_DSN: string;
|
SENTRY_DSN: string;
|
||||||
SITE_URL: string;
|
SITE_URL: string;
|
||||||
SMTP_HOST: string;
|
SMTP_HOST: string;
|
||||||
|
|||||||
11457
backend/package-lock.json
generated
@@ -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.2",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"stripe": "^10.7.0",
|
"stripe": "^10.7.0",
|
||||||
|
|||||||
@@ -16,22 +16,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 CLIENT_SLUG_VERCEL= process.env.CLIENT_SLUG_VERCEL!;
|
||||||
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
|
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
|
||||||
const POSTHOG_PROJECT_API_KEY =
|
const POSTHOG_PROJECT_API_KEY =
|
||||||
process.env.POSTHOG_PROJECT_API_KEY! ||
|
process.env.POSTHOG_PROJECT_API_KEY! ||
|
||||||
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
|
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
|
||||||
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
|
|
||||||
const PUBLIC_KEY = process.env.PUBLIC_KEY!;
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN!;
|
const SENTRY_DSN = process.env.SENTRY_DSN!;
|
||||||
const SITE_URL = process.env.SITE_URL!;
|
const SITE_URL = process.env.SITE_URL!;
|
||||||
const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com';
|
const SMTP_HOST = process.env.SMTP_HOST!;
|
||||||
const SMTP_PORT = process.env.SMTP_PORT! || 587;
|
const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false;
|
||||||
const SMTP_NAME = process.env.SMTP_NAME!;
|
const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587;
|
||||||
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
|
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
|
||||||
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
|
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
|
||||||
|
const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!;
|
||||||
|
const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical';
|
||||||
const STRIPE_PRODUCT_CARD_AUTH = process.env.STRIPE_PRODUCT_CARD_AUTH!;
|
const STRIPE_PRODUCT_CARD_AUTH = process.env.STRIPE_PRODUCT_CARD_AUTH!;
|
||||||
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
|
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
|
||||||
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
|
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
|
||||||
@@ -58,21 +60,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,
|
CLIENT_SLUG_VERCEL,
|
||||||
POSTHOG_HOST,
|
POSTHOG_HOST,
|
||||||
POSTHOG_PROJECT_API_KEY,
|
POSTHOG_PROJECT_API_KEY,
|
||||||
PRIVATE_KEY,
|
|
||||||
PUBLIC_KEY,
|
|
||||||
SENTRY_DSN,
|
SENTRY_DSN,
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
SMTP_HOST,
|
SMTP_HOST,
|
||||||
SMTP_PORT,
|
SMTP_PORT,
|
||||||
SMTP_NAME,
|
SMTP_SECURE,
|
||||||
SMTP_USERNAME,
|
SMTP_USERNAME,
|
||||||
SMTP_PASSWORD,
|
SMTP_PASSWORD,
|
||||||
|
SMTP_FROM_ADDRESS,
|
||||||
|
SMTP_FROM_NAME,
|
||||||
STRIPE_PRODUCT_CARD_AUTH,
|
STRIPE_PRODUCT_CARD_AUTH,
|
||||||
STRIPE_PRODUCT_PRO,
|
STRIPE_PRODUCT_PRO,
|
||||||
STRIPE_PRODUCT_STARTER,
|
STRIPE_PRODUCT_STARTER,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Request, Response } from 'express';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Key } from '../models';
|
import { Key } from '../models';
|
||||||
import { findMembership } from '../helpers/membership';
|
import { findMembership } from '../helpers/membership';
|
||||||
import { PUBLIC_KEY } from '../config';
|
|
||||||
import { GRANTED } from '../variables';
|
import { GRANTED } from '../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,16 +83,4 @@ export const getLatestKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send(resObj);
|
return res.status(200).send(resObj);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return public key of Infisical
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getPublicKeyInfisical = async (req: Request, res: Response) => {
|
|
||||||
return res.status(200).send({
|
|
||||||
publicKey: PUBLIC_KEY
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -58,7 +58,8 @@ export const createServiceToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
token = createToken({
|
token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
serviceTokenId: serviceToken._id.toString()
|
serviceTokenId: serviceToken._id.toString(),
|
||||||
|
workspaceId
|
||||||
},
|
},
|
||||||
expiresIn: expiresIn,
|
expiresIn: expiresIn,
|
||||||
secret: JWT_SERVICE_SECRET
|
secret: JWT_SERVICE_SECRET
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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 { SMTP_NAME, SMTP_USERNAME } from '../config';
|
import { SMTP_FROM_NAME, SMTP_FROM_ADDRESS } from '../config';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
let smtpTransporter: nodemailer.Transporter;
|
let smtpTransporter: nodemailer.Transporter;
|
||||||
@@ -34,7 +34,7 @@ const sendMail = async ({
|
|||||||
const htmlToSend = temp(substitutions);
|
const htmlToSend = temp(substitutions);
|
||||||
|
|
||||||
await smtpTransporter.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
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Octokit } from '@octokit/rest';
|
||||||
|
import { IIntegrationAuth } from '../models';
|
||||||
import {
|
import {
|
||||||
IIntegrationAuth
|
INTEGRATION_HEROKU,
|
||||||
} from '../models';
|
INTEGRATION_VERCEL,
|
||||||
import {
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_API_URL
|
||||||
INTEGRATION_NETLIFY_API_URL
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
|
interface GitHubApp {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for integration named [integration]
|
* Return list of names of apps for integration named [integration]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -21,47 +26,51 @@ import {
|
|||||||
* @returns {String} apps.name - name of integration app
|
* @returns {String} apps.name - name of integration app
|
||||||
*/
|
*/
|
||||||
const getApps = async ({
|
const getApps = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
interface App {
|
||||||
interface App {
|
name: string;
|
||||||
name: string;
|
siteId?: string;
|
||||||
siteId?: string;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let apps: App[]; // TODO: add type and define payloads for apps
|
let apps: App[]; // TODO: add type and define payloads for apps
|
||||||
try {
|
try {
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
apps = await getAppsHeroku({
|
apps = await getAppsHeroku({
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
apps = await getAppsVercel({
|
apps = await getAppsVercel({
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
apps = await getAppsNetlify({
|
apps = await getAppsNetlify({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
|
apps = await getAppsGithub({
|
||||||
} catch (err) {
|
integrationAuth,
|
||||||
Sentry.setUser(null);
|
accessToken
|
||||||
Sentry.captureException(err);
|
});
|
||||||
throw new Error('Failed to get integration apps');
|
break;
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
return apps;
|
Sentry.setUser(null);
|
||||||
}
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get integration apps');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for Heroku integration
|
* Return list of names of apps for Heroku integration
|
||||||
@@ -70,31 +79,29 @@ const getApps = async ({
|
|||||||
* @returns {Object[]} apps - names of Heroku apps
|
* @returns {Object[]} apps - names of Heroku apps
|
||||||
* @returns {String} apps.name - name of Heroku app
|
* @returns {String} apps.name - name of Heroku app
|
||||||
*/
|
*/
|
||||||
const getAppsHeroku = async ({
|
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
||||||
accessToken
|
let apps;
|
||||||
}: {
|
try {
|
||||||
accessToken: string;
|
const res = (
|
||||||
}) => {
|
await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
|
||||||
let apps;
|
headers: {
|
||||||
try {
|
Accept: 'application/vnd.heroku+json; version=3',
|
||||||
const res = (await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
|
Authorization: `Bearer ${accessToken}`
|
||||||
headers: {
|
}
|
||||||
Accept: 'application/vnd.heroku+json; version=3',
|
})
|
||||||
Authorization: `Bearer ${accessToken}`
|
).data;
|
||||||
}
|
|
||||||
})).data;
|
apps = res.map((a: any) => ({
|
||||||
|
name: a.name
|
||||||
apps = res.map((a: any) => ({
|
}));
|
||||||
name: a.name
|
} catch (err) {
|
||||||
}));
|
Sentry.setUser(null);
|
||||||
} catch (err) {
|
Sentry.captureException(err);
|
||||||
Sentry.setUser(null);
|
throw new Error('Failed to get Heroku integration apps');
|
||||||
Sentry.captureException(err);
|
}
|
||||||
throw new Error('Failed to get Heroku integration apps');
|
|
||||||
}
|
return apps;
|
||||||
|
};
|
||||||
return apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for Vercel integration
|
* Return list of names of apps for Vercel integration
|
||||||
@@ -103,30 +110,28 @@ const getAppsHeroku = async ({
|
|||||||
* @returns {Object[]} apps - names of Vercel apps
|
* @returns {Object[]} apps - names of Vercel apps
|
||||||
* @returns {String} apps.name - name of Vercel app
|
* @returns {String} apps.name - name of Vercel app
|
||||||
*/
|
*/
|
||||||
const getAppsVercel = async ({
|
const getAppsVercel = async ({ accessToken }: { accessToken: string }) => {
|
||||||
accessToken
|
let apps;
|
||||||
}: {
|
try {
|
||||||
accessToken: string;
|
const res = (
|
||||||
}) => {
|
await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
|
||||||
let apps;
|
headers: {
|
||||||
try {
|
Authorization: `Bearer ${accessToken}`
|
||||||
const res = (await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
|
}
|
||||||
headers: {
|
})
|
||||||
Authorization: `Bearer ${accessToken}`
|
).data;
|
||||||
}
|
|
||||||
})).data;
|
apps = res.projects.map((a: any) => ({
|
||||||
|
name: a.name
|
||||||
apps = res.projects.map((a: any) => ({
|
}));
|
||||||
name: a.name
|
} catch (err) {
|
||||||
}));
|
Sentry.setUser(null);
|
||||||
} catch (err) {
|
Sentry.captureException(err);
|
||||||
Sentry.setUser(null);
|
throw new Error('Failed to get Vercel integration apps');
|
||||||
Sentry.captureException(err);
|
}
|
||||||
throw new Error('Failed to get Vercel integration apps');
|
|
||||||
}
|
return apps;
|
||||||
|
};
|
||||||
return apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of sites for Netlify integration
|
* Return list of names of sites for Netlify integration
|
||||||
@@ -136,34 +141,73 @@ const getAppsVercel = async ({
|
|||||||
* @returns {String} apps.name - name of Netlify site
|
* @returns {String} apps.name - name of Netlify site
|
||||||
*/
|
*/
|
||||||
const getAppsNetlify = async ({
|
const getAppsNetlify = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let apps;
|
let apps;
|
||||||
try {
|
try {
|
||||||
const res = (await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
|
const res = (
|
||||||
headers: {
|
await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
|
||||||
Authorization: `Bearer ${accessToken}`
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${accessToken}`
|
||||||
})).data;
|
}
|
||||||
|
})
|
||||||
apps = res.map((a: any) => ({
|
).data;
|
||||||
name: a.name,
|
|
||||||
siteId: a.site_id
|
|
||||||
}));
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to get Netlify integration apps');
|
|
||||||
}
|
|
||||||
|
|
||||||
return apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
apps = res.map((a: any) => ({
|
||||||
getApps
|
name: a.name,
|
||||||
}
|
siteId: a.site_id
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get Netlify integration apps');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of names of repositories for Github integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for Netlify API
|
||||||
|
* @returns {Object[]} apps - names of Netlify sites
|
||||||
|
* @returns {String} apps.name - name of Netlify site
|
||||||
|
*/
|
||||||
|
const getAppsGithub = async ({
|
||||||
|
integrationAuth,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integrationAuth: IIntegrationAuth;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
let apps;
|
||||||
|
try {
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
const repos = (await octokit.request(
|
||||||
|
'GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}',
|
||||||
|
{}
|
||||||
|
)).data;
|
||||||
|
|
||||||
|
apps = repos
|
||||||
|
.filter((a:any) => a.permissions.admin === true)
|
||||||
|
.map((a: any) => ({
|
||||||
|
name: a.name
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get Github repos');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getApps };
|
||||||
|
|||||||
@@ -1,46 +1,57 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
ACTION_PUSH_TO_HEROKU
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
|
INTEGRATION_GITHUB_API_URL
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
CLIENT_SECRET_HEROKU,
|
CLIENT_ID_VERCEL,
|
||||||
CLIENT_ID_VERCEL,
|
CLIENT_ID_NETLIFY,
|
||||||
CLIENT_ID_NETLIFY,
|
CLIENT_ID_GITHUB,
|
||||||
CLIENT_SECRET_VERCEL,
|
CLIENT_SECRET_HEROKU,
|
||||||
CLIENT_SECRET_NETLIFY
|
CLIENT_SECRET_VERCEL,
|
||||||
|
CLIENT_SECRET_NETLIFY,
|
||||||
|
CLIENT_SECRET_GITHUB
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
import { user } from '../routes';
|
||||||
|
|
||||||
interface ExchangeCodeHerokuResponse {
|
interface ExchangeCodeHerokuResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
expires_in: number;
|
expires_in: number;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
session_nonce?: string;
|
session_nonce?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExchangeCodeVercelResponse {
|
interface ExchangeCodeVercelResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
access_token: string;
|
access_token: string;
|
||||||
installation_id: string;
|
installation_id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
team_id?: string;
|
team_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExchangeCodeNetlifyResponse {
|
interface ExchangeCodeNetlifyResponse {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
token_type: string;
|
token_type: string;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
scope: string;
|
scope: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExchangeCodeGithubResponse {
|
||||||
|
access_token: string;
|
||||||
|
scope: string;
|
||||||
|
token_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,40 +67,45 @@ interface ExchangeCodeNetlifyResponse {
|
|||||||
* @returns {String} obj.action - integration action for bot sequence
|
* @returns {String} obj.action - integration action for bot sequence
|
||||||
*/
|
*/
|
||||||
const exchangeCode = async ({
|
const exchangeCode = async ({
|
||||||
integration,
|
integration,
|
||||||
code
|
code
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integration: string;
|
||||||
code: string;
|
code: string;
|
||||||
}) => {
|
}) => {
|
||||||
let obj = {} as any;
|
let obj = {} as any;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (integration) {
|
switch (integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
obj = await exchangeCodeHeroku({
|
obj = await exchangeCodeHeroku({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
obj = await exchangeCodeVercel({
|
obj = await exchangeCodeVercel({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
obj = await exchangeCodeNetlify({
|
obj = await exchangeCodeNetlify({
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
} catch (err) {
|
obj = await exchangeCodeGithub({
|
||||||
Sentry.setUser(null);
|
code
|
||||||
Sentry.captureException(err);
|
});
|
||||||
throw new Error('Failed OAuth2 code-token exchange');
|
break;
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
return obj;
|
Sentry.setUser(null);
|
||||||
}
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed OAuth2 code-token exchange');
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
|
||||||
@@ -144,35 +160,33 @@ const exchangeCodeHeroku = async ({
|
|||||||
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
||||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
*/
|
*/
|
||||||
const exchangeCodeVercel = async ({
|
const exchangeCodeVercel = async ({ code }: { code: string }) => {
|
||||||
code
|
let res: ExchangeCodeVercelResponse;
|
||||||
}: {
|
try {
|
||||||
code: string;
|
res = (
|
||||||
}) => {
|
await axios.post(
|
||||||
let res: ExchangeCodeVercelResponse;
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
try {
|
new URLSearchParams({
|
||||||
res = (await axios.post(
|
code: code,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
client_id: CLIENT_ID_VERCEL,
|
||||||
new URLSearchParams({
|
client_secret: CLIENT_SECRET_VERCEL,
|
||||||
code: code,
|
redirect_uri: `${SITE_URL}/vercel`
|
||||||
client_id: CLIENT_ID_VERCEL,
|
} as any)
|
||||||
client_secret: CLIENT_SECRET_VERCEL,
|
)
|
||||||
redirect_uri: `${SITE_URL}/vercel`
|
).data;
|
||||||
} as any)
|
} catch (err) {
|
||||||
)).data;
|
Sentry.setUser(null);
|
||||||
} catch (err) {
|
Sentry.captureException(err);
|
||||||
Sentry.setUser(null);
|
throw new Error('Failed OAuth2 code-token exchange with Vercel');
|
||||||
Sentry.captureException(err);
|
}
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Vercel');
|
|
||||||
}
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
return ({
|
refreshToken: null,
|
||||||
accessToken: res.access_token,
|
accessExpiresAt: null,
|
||||||
refreshToken: null,
|
teamId: res.team_id
|
||||||
accessExpiresAt: null,
|
};
|
||||||
teamId: res.team_id
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
|
||||||
@@ -184,58 +198,89 @@ const exchangeCodeVercel = async ({
|
|||||||
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
* @returns {String} obj2.refreshToken - refresh token for Heroku API
|
||||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
*/
|
*/
|
||||||
const exchangeCodeNetlify = async ({
|
const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
||||||
code
|
let res: ExchangeCodeNetlifyResponse;
|
||||||
}: {
|
let accountId;
|
||||||
code: string;
|
try {
|
||||||
}) => {
|
res = (
|
||||||
let res: ExchangeCodeNetlifyResponse;
|
await axios.post(
|
||||||
let accountId;
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
try {
|
new URLSearchParams({
|
||||||
res = (await axios.post(
|
grant_type: 'authorization_code',
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
code: code,
|
||||||
new URLSearchParams({
|
client_id: CLIENT_ID_NETLIFY,
|
||||||
grant_type: 'authorization_code',
|
client_secret: CLIENT_SECRET_NETLIFY,
|
||||||
code: code,
|
redirect_uri: `${SITE_URL}/netlify`
|
||||||
client_id: CLIENT_ID_NETLIFY,
|
} as any)
|
||||||
client_secret: CLIENT_SECRET_NETLIFY,
|
)
|
||||||
redirect_uri: `${SITE_URL}/netlify`
|
).data;
|
||||||
} as any)
|
|
||||||
)).data;
|
|
||||||
|
|
||||||
const res2 = await axios.get(
|
const res2 = await axios.get('https://api.netlify.com/api/v1/sites', {
|
||||||
'https://api.netlify.com/api/v1/sites',
|
headers: {
|
||||||
{
|
Authorization: `Bearer ${res.access_token}`
|
||||||
headers: {
|
}
|
||||||
Authorization: `Bearer ${res.access_token}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const res3 = (await axios.get(
|
|
||||||
'https://api.netlify.com/api/v1/accounts',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${res.access_token}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)).data;
|
|
||||||
|
|
||||||
accountId = res3[0].id;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Netlify');
|
|
||||||
}
|
|
||||||
|
|
||||||
return ({
|
|
||||||
accessToken: res.access_token,
|
|
||||||
refreshToken: res.refresh_token,
|
|
||||||
accountId
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
const res3 = (
|
||||||
exchangeCode
|
await axios.get('https://api.netlify.com/api/v1/accounts', {
|
||||||
}
|
headers: {
|
||||||
|
Authorization: `Bearer ${res.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
accountId = res3[0].id;
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed OAuth2 code-token exchange with Netlify');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
|
refreshToken: res.refresh_token,
|
||||||
|
accountId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Github
|
||||||
|
* code-token exchange
|
||||||
|
* @param {Object} obj1
|
||||||
|
* @param {Object} obj1.code - code for code-token exchange
|
||||||
|
* @returns {Object} obj2
|
||||||
|
* @returns {String} obj2.accessToken - access token for Github API
|
||||||
|
* @returns {String} obj2.refreshToken - refresh token for Github API
|
||||||
|
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||||
|
*/
|
||||||
|
const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||||
|
let res: ExchangeCodeGithubResponse;
|
||||||
|
try {
|
||||||
|
res = (
|
||||||
|
await axios.get(INTEGRATION_GITHUB_TOKEN_URL, {
|
||||||
|
params: {
|
||||||
|
client_id: CLIENT_ID_GITHUB,
|
||||||
|
client_secret: CLIENT_SECRET_GITHUB,
|
||||||
|
code: code,
|
||||||
|
redirect_uri: `${SITE_URL}/github`
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed OAuth2 code-token exchange with Github');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: res.access_token,
|
||||||
|
refreshToken: null,
|
||||||
|
accessExpiresAt: null
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { exchangeCode };
|
||||||
|
|||||||
@@ -13,44 +13,44 @@ import {
|
|||||||
* named [integration]
|
* named [integration]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.integration - name of integration
|
* @param {String} obj.integration - name of integration
|
||||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
||||||
*/
|
*/
|
||||||
const exchangeRefresh = async ({
|
const exchangeRefresh = async ({
|
||||||
integration,
|
integration,
|
||||||
refreshToken
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integration: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let accessToken;
|
let accessToken;
|
||||||
try {
|
try {
|
||||||
switch (integration) {
|
switch (integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
accessToken = await exchangeRefreshHeroku({
|
accessToken = await exchangeRefreshHeroku({
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to get new OAuth2 access token');
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
return accessToken;
|
Sentry.setUser(null);
|
||||||
}
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get new OAuth2 access token');
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
* Heroku integration
|
* Heroku integration
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const exchangeRefreshHeroku = async ({
|
const exchangeRefreshHeroku = async ({
|
||||||
refreshToken
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let accessToken;
|
let accessToken;
|
||||||
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
||||||
@@ -64,16 +64,14 @@ const exchangeRefreshHeroku = async ({
|
|||||||
} as any)
|
} as any)
|
||||||
);
|
);
|
||||||
|
|
||||||
accessToken = res.data.access_token;
|
accessToken = res.data.access_token;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to get new OAuth2 access token for Heroku');
|
throw new Error('Failed to get new OAuth2 access token for Heroku');
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
return accessToken;
|
||||||
exchangeRefresh
|
};
|
||||||
}
|
|
||||||
|
export { exchangeRefresh };
|
||||||
|
|||||||
@@ -1,50 +1,47 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { IIntegrationAuth, IntegrationAuth, Integration } from '../models';
|
||||||
import {
|
import {
|
||||||
IIntegrationAuth,
|
INTEGRATION_HEROKU,
|
||||||
IntegrationAuth,
|
INTEGRATION_VERCEL,
|
||||||
Integration
|
INTEGRATION_NETLIFY,
|
||||||
} from '../models';
|
INTEGRATION_GITHUB
|
||||||
import {
|
|
||||||
INTEGRATION_HEROKU,
|
|
||||||
INTEGRATION_VERCEL,
|
|
||||||
INTEGRATION_NETLIFY
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
const revokeAccess = async ({
|
const revokeAccess = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth,
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
// add any integration-specific revocation logic
|
// add any integration-specific revocation logic
|
||||||
switch (integrationAuth.integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
|
break;
|
||||||
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
|
||||||
_id: integrationAuth._id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deletedIntegrationAuth) {
|
|
||||||
await Integration.deleteMany({
|
|
||||||
integrationAuth: deletedIntegrationAuth._id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to delete integration authorization');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
||||||
revokeAccess
|
_id: integrationAuth._id
|
||||||
}
|
});
|
||||||
|
|
||||||
|
if (deletedIntegrationAuth) {
|
||||||
|
await Integration.deleteMany({
|
||||||
|
integrationAuth: deletedIntegrationAuth._id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to delete integration authorization');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { revokeAccess };
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Octokit } from '@octokit/rest';
|
||||||
|
// import * as sodium from 'libsodium-wrappers';
|
||||||
|
import sodium from 'libsodium-wrappers';
|
||||||
|
// const sodium = require('libsodium-wrappers');
|
||||||
|
import { IIntegration, IIntegrationAuth } from '../models';
|
||||||
import {
|
import {
|
||||||
IIntegration, IIntegrationAuth
|
INTEGRATION_HEROKU,
|
||||||
} from '../models';
|
INTEGRATION_VERCEL,
|
||||||
import {
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_API_URL
|
||||||
INTEGRATION_NETLIFY_API_URL
|
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
import { access, appendFile } from 'fs';
|
||||||
|
|
||||||
// TODO: need a helper function in the future to handle integration
|
// TODO: need a helper function in the future to handle integration
|
||||||
// envar priorities (i.e. prioritize secrets within integration or those on Infisical)
|
// envar priorities (i.e. prioritize secrets within integration or those on Infisical)
|
||||||
@@ -26,49 +31,54 @@ import {
|
|||||||
* @param {String} obj.accessToken - access token for integration
|
* @param {String} obj.accessToken - access token for integration
|
||||||
*/
|
*/
|
||||||
const syncSecrets = async ({
|
const syncSecrets = async ({
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken,
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integration: IIntegration;
|
integration: IIntegration;
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
secrets: any;
|
secrets: any;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
switch (integration.integration) {
|
switch (integration.integration) {
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
await syncSecretsHeroku({
|
await syncSecretsHeroku({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_VERCEL:
|
case INTEGRATION_VERCEL:
|
||||||
await syncSecretsVercel({
|
await syncSecretsVercel({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_NETLIFY:
|
case INTEGRATION_NETLIFY:
|
||||||
await syncSecretsNetlify({
|
await syncSecretsNetlify({
|
||||||
integration,
|
integration,
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
case INTEGRATION_GITHUB:
|
||||||
|
await syncSecretsGitHub({
|
||||||
// TODO: set integration to inactive if it was not synced correctly (send alert?)
|
integration,
|
||||||
} catch (err) {
|
secrets,
|
||||||
Sentry.setUser(null);
|
accessToken
|
||||||
Sentry.captureException(err);
|
});
|
||||||
throw new Error('Failed to sync secrets to integration');
|
break;
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to integration');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to Heroku [app]
|
* Sync/push [secrets] to Heroku [app]
|
||||||
@@ -77,47 +87,49 @@ const syncSecrets = async ({
|
|||||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
*/
|
*/
|
||||||
const syncSecretsHeroku = async ({
|
const syncSecretsHeroku = async ({
|
||||||
integration,
|
integration,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integration: IIntegration,
|
integration: IIntegration;
|
||||||
secrets: any;
|
secrets: any;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const herokuSecrets = (await axios.get(
|
const herokuSecrets = (
|
||||||
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
await axios.get(
|
||||||
{
|
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||||
headers: {
|
{
|
||||||
Accept: 'application/vnd.heroku+json; version=3',
|
headers: {
|
||||||
Authorization: `Bearer ${accessToken}`
|
Accept: 'application/vnd.heroku+json; version=3',
|
||||||
}
|
Authorization: `Bearer ${accessToken}`
|
||||||
}
|
}
|
||||||
)).data;
|
}
|
||||||
|
)
|
||||||
Object.keys(herokuSecrets).forEach(key => {
|
).data;
|
||||||
if (!(key in secrets)) {
|
|
||||||
secrets[key] = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await axios.patch(
|
Object.keys(herokuSecrets).forEach((key) => {
|
||||||
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
if (!(key in secrets)) {
|
||||||
secrets,
|
secrets[key] = null;
|
||||||
{
|
}
|
||||||
headers: {
|
});
|
||||||
Accept: 'application/vnd.heroku+json; version=3',
|
|
||||||
Authorization: `Bearer ${accessToken}`
|
await axios.patch(
|
||||||
}
|
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
|
||||||
}
|
secrets,
|
||||||
);
|
{
|
||||||
} catch (err) {
|
headers: {
|
||||||
Sentry.setUser(null);
|
Accept: 'application/vnd.heroku+json; version=3',
|
||||||
Sentry.captureException(err);
|
Authorization: `Bearer ${accessToken}`
|
||||||
throw new Error('Failed to sync secrets to Heroku');
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to Heroku');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to Heroku [app]
|
* Sync/push [secrets] to Heroku [app]
|
||||||
@@ -474,8 +486,120 @@ const syncSecretsNetlify = async ({
|
|||||||
throw new Error('Failed to sync secrets to Heroku');
|
throw new Error('Failed to sync secrets to Heroku');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export {
|
/**
|
||||||
syncSecrets
|
* Sync/push [secrets] to GitHub [repo]
|
||||||
}
|
* @param {Object} obj
|
||||||
|
* @param {IIntegration} obj.integration - integration details
|
||||||
|
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
|
||||||
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
|
*/
|
||||||
|
const syncSecretsGitHub = async ({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integration: IIntegration;
|
||||||
|
secrets: any;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
interface GitHubRepoKey {
|
||||||
|
key_id: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubSecret {
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubSecretRes {
|
||||||
|
[index: string]: GitHubSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSecrets: GitHubSecret[] = [];
|
||||||
|
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = (await octokit.request('GET /user', {})).data;
|
||||||
|
|
||||||
|
const repoPublicKey: GitHubRepoKey = (await octokit.request(
|
||||||
|
'GET /repos/{owner}/{repo}/actions/secrets/public-key',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app
|
||||||
|
}
|
||||||
|
)).data;
|
||||||
|
|
||||||
|
// // Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key
|
||||||
|
const encryptedSecrets: GitHubSecretRes = (await octokit.request(
|
||||||
|
'GET /repos/{owner}/{repo}/actions/secrets',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.data
|
||||||
|
.secrets
|
||||||
|
.reduce((obj: any, secret: any) => ({
|
||||||
|
...obj,
|
||||||
|
[secret.name]: secret
|
||||||
|
}), {});
|
||||||
|
|
||||||
|
Object.keys(encryptedSecrets).map(async (key) => {
|
||||||
|
if (!(key in secrets)) {
|
||||||
|
await octokit.request(
|
||||||
|
'DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app,
|
||||||
|
secret_name: key
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(secrets).map((key) => {
|
||||||
|
// let encryptedSecret;
|
||||||
|
sodium.ready.then(async () => {
|
||||||
|
// convert secret & base64 key to Uint8Array.
|
||||||
|
const binkey = sodium.from_base64(
|
||||||
|
repoPublicKey.key,
|
||||||
|
sodium.base64_variants.ORIGINAL
|
||||||
|
);
|
||||||
|
const binsec = sodium.from_string(secrets[key]);
|
||||||
|
|
||||||
|
// encrypt secret using libsodium
|
||||||
|
const encBytes = sodium.crypto_box_seal(binsec, binkey);
|
||||||
|
|
||||||
|
// convert encrypted Uint8Array to base64
|
||||||
|
const encryptedSecret = sodium.to_base64(
|
||||||
|
encBytes,
|
||||||
|
sodium.base64_variants.ORIGINAL
|
||||||
|
);
|
||||||
|
|
||||||
|
await octokit.request(
|
||||||
|
'PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}',
|
||||||
|
{
|
||||||
|
owner: user.login,
|
||||||
|
repo: integration.app,
|
||||||
|
secret_name: key,
|
||||||
|
encrypted_value: encryptedSecret,
|
||||||
|
key_id: repoPublicKey.key_id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to GitHub');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { syncSecrets };
|
||||||
@@ -1,77 +1,83 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
ENV_DEV,
|
ENV_DEV,
|
||||||
ENV_TESTING,
|
ENV_TESTING,
|
||||||
ENV_STAGING,
|
ENV_STAGING,
|
||||||
ENV_PROD,
|
ENV_PROD,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_GITHUB
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
export interface IIntegration {
|
export interface IIntegration {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: 'dev' | 'test' | 'staging' | 'prod';
|
environment: 'dev' | 'test' | 'staging' | 'prod';
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
app: string;
|
app: string;
|
||||||
target: string;
|
target: string;
|
||||||
context: string;
|
context: string;
|
||||||
siteId: string;
|
siteId: string;
|
||||||
integration: 'heroku' | 'vercel' | 'netlify';
|
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
|
||||||
integrationAuth: Types.ObjectId;
|
integrationAuth: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const integrationSchema = new Schema<IIntegration>(
|
const integrationSchema = new Schema<IIntegration>(
|
||||||
{
|
{
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Workspace',
|
ref: 'Workspace',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
environment: {
|
environment: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
|
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
isActive: {
|
isActive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
app: { // name of app in provider
|
app: {
|
||||||
type: String,
|
// name of app in provider
|
||||||
default: null
|
type: String,
|
||||||
},
|
default: null
|
||||||
target: { // vercel-specific target (environment)
|
},
|
||||||
type: String,
|
target: {
|
||||||
default: null
|
// vercel-specific target (environment)
|
||||||
},
|
type: String,
|
||||||
context: { // netlify-specific context (deploy)
|
default: null
|
||||||
type: String,
|
},
|
||||||
default: null
|
context: {
|
||||||
},
|
// netlify-specific context (deploy)
|
||||||
siteId: { // netlify-specific site (app) id
|
type: String,
|
||||||
type: String,
|
default: null
|
||||||
default: null
|
},
|
||||||
},
|
siteId: {
|
||||||
integration: {
|
// netlify-specific site (app) id
|
||||||
type: String,
|
type: String,
|
||||||
enum: [
|
default: null
|
||||||
INTEGRATION_HEROKU,
|
},
|
||||||
INTEGRATION_VERCEL,
|
integration: {
|
||||||
INTEGRATION_NETLIFY
|
type: String,
|
||||||
],
|
enum: [
|
||||||
required: true
|
INTEGRATION_HEROKU,
|
||||||
},
|
INTEGRATION_VERCEL,
|
||||||
integrationAuth: {
|
INTEGRATION_NETLIFY,
|
||||||
type: Schema.Types.ObjectId,
|
INTEGRATION_GITHUB
|
||||||
ref: 'IntegrationAuth',
|
],
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
},
|
integrationAuth: {
|
||||||
{
|
type: Schema.Types.ObjectId,
|
||||||
timestamps: true
|
ref: 'IntegrationAuth',
|
||||||
}
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const Integration = model<IIntegration>('Integration', integrationSchema);
|
const Integration = model<IIntegration>('Integration', integrationSchema);
|
||||||
|
|||||||
@@ -1,83 +1,87 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
|
INTEGRATION_GITHUB
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
|
||||||
export interface IIntegrationAuth {
|
export interface IIntegrationAuth {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
integration: 'heroku' | 'vercel' | 'netlify';
|
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
|
||||||
teamId: string;
|
teamId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
refreshCiphertext?: string;
|
refreshCiphertext?: string;
|
||||||
refreshIV?: string;
|
refreshIV?: string;
|
||||||
refreshTag?: string;
|
refreshTag?: string;
|
||||||
accessCiphertext?: string;
|
accessCiphertext?: string;
|
||||||
accessIV?: string;
|
accessIV?: string;
|
||||||
accessTag?: string;
|
accessTag?: string;
|
||||||
accessExpiresAt?: Date;
|
accessExpiresAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||||
{
|
{
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
integration: {
|
integration: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [
|
enum: [
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY,
|
||||||
],
|
INTEGRATION_GITHUB
|
||||||
required: true
|
],
|
||||||
},
|
required: true
|
||||||
teamId: { // vercel-specific integration param
|
},
|
||||||
type: String
|
teamId: {
|
||||||
},
|
// vercel-specific integration param
|
||||||
accountId: { // netlify-specific integration param
|
type: String
|
||||||
type: String
|
},
|
||||||
},
|
accountId: {
|
||||||
refreshCiphertext: {
|
// netlify-specific integration param
|
||||||
type: String,
|
type: String
|
||||||
select: false
|
},
|
||||||
},
|
refreshCiphertext: {
|
||||||
refreshIV: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
refreshIV: {
|
||||||
refreshTag: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
refreshTag: {
|
||||||
accessCiphertext: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
accessCiphertext: {
|
||||||
accessIV: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
accessIV: {
|
||||||
accessTag: {
|
type: String,
|
||||||
type: String,
|
select: false
|
||||||
select: false
|
},
|
||||||
},
|
accessTag: {
|
||||||
accessExpiresAt: {
|
type: String,
|
||||||
type: Date,
|
select: false
|
||||||
select: false
|
},
|
||||||
}
|
accessExpiresAt: {
|
||||||
},
|
type: Date,
|
||||||
{
|
select: false
|
||||||
timestamps: true
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const IntegrationAuth = model<IIntegrationAuth>(
|
const IntegrationAuth = model<IIntegrationAuth>(
|
||||||
'IntegrationAuth',
|
'IntegrationAuth',
|
||||||
integrationAuthSchema
|
integrationAuthSchema
|
||||||
);
|
);
|
||||||
|
|
||||||
export default IntegrationAuth;
|
export default IntegrationAuth;
|
||||||
|
|||||||
@@ -34,6 +34,4 @@ router.get(
|
|||||||
keyController.getLatestKey
|
keyController.getLatestKey
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } from '../config';
|
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config';
|
||||||
|
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from '../variables';
|
||||||
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
import SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ const mailOpts: SMTPConnection.Options = {
|
|||||||
host: SMTP_HOST,
|
host: SMTP_HOST,
|
||||||
port: SMTP_PORT as number
|
port: SMTP_PORT as number
|
||||||
};
|
};
|
||||||
|
|
||||||
if (SMTP_USERNAME && SMTP_PASSWORD) {
|
if (SMTP_USERNAME && SMTP_PASSWORD) {
|
||||||
mailOpts.auth = {
|
mailOpts.auth = {
|
||||||
user: SMTP_USERNAME,
|
user: SMTP_USERNAME,
|
||||||
@@ -14,6 +16,23 @@ if (SMTP_USERNAME && SMTP_PASSWORD) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SMTP_SECURE) {
|
||||||
|
switch (SMTP_HOST) {
|
||||||
|
case SMTP_HOST_SENDGRID:
|
||||||
|
mailOpts.requireTLS = true;
|
||||||
|
break;
|
||||||
|
case SMTP_HOST_MAILGUN:
|
||||||
|
mailOpts.requireTLS = true;
|
||||||
|
mailOpts.tls = {
|
||||||
|
ciphers: 'TLSv1.2'
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mailOpts.secure = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const initSmtp = () => {
|
export const initSmtp = () => {
|
||||||
const transporter = nodemailer.createTransport(mailOpts);
|
const transporter = nodemailer.createTransport(mailOpts);
|
||||||
transporter
|
transporter
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
const ACTION_PUSH_TO_HEROKU = 'pushToHeroku';
|
|
||||||
|
|
||||||
export {
|
|
||||||
ACTION_PUSH_TO_HEROKU
|
|
||||||
}
|
|
||||||
@@ -1,79 +1,75 @@
|
|||||||
import {
|
import {
|
||||||
ENV_DEV,
|
ENV_DEV,
|
||||||
ENV_TESTING,
|
ENV_TESTING,
|
||||||
ENV_STAGING,
|
ENV_STAGING,
|
||||||
ENV_PROD,
|
ENV_PROD,
|
||||||
ENV_SET
|
ENV_SET
|
||||||
} from './environment';
|
} from './environment';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_OAUTH2,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_OAUTH2,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_OPTIONS
|
INTEGRATION_VERCEL_API_URL,
|
||||||
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
|
INTEGRATION_GITHUB_API_URL,
|
||||||
|
INTEGRATION_OPTIONS
|
||||||
} from './integration';
|
} from './integration';
|
||||||
import {
|
import {
|
||||||
OWNER,
|
OWNER,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
INVITED,
|
INVITED,
|
||||||
ACCEPTED,
|
ACCEPTED,
|
||||||
COMPLETED,
|
COMPLETED,
|
||||||
GRANTED
|
GRANTED
|
||||||
} from './organization';
|
} from './organization';
|
||||||
import {
|
import { SECRET_SHARED, SECRET_PERSONAL } from './secret';
|
||||||
SECRET_SHARED,
|
import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event';
|
||||||
SECRET_PERSONAL
|
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from './smtp';
|
||||||
} from './secret';
|
import { PLAN_STARTER, PLAN_PRO } from './stripe';
|
||||||
import {
|
|
||||||
PLAN_STARTER,
|
|
||||||
PLAN_PRO
|
|
||||||
} from './stripe';
|
|
||||||
import {
|
|
||||||
EVENT_PUSH_SECRETS,
|
|
||||||
EVENT_PULL_SECRETS
|
|
||||||
} from './event';
|
|
||||||
import {
|
|
||||||
ACTION_PUSH_TO_HEROKU
|
|
||||||
} from './action';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
OWNER,
|
OWNER,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
INVITED,
|
INVITED,
|
||||||
ACCEPTED,
|
ACCEPTED,
|
||||||
COMPLETED,
|
COMPLETED,
|
||||||
GRANTED,
|
GRANTED,
|
||||||
PLAN_STARTER,
|
SECRET_SHARED,
|
||||||
PLAN_PRO,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED,
|
ENV_DEV,
|
||||||
SECRET_PERSONAL,
|
ENV_TESTING,
|
||||||
ENV_DEV,
|
ENV_STAGING,
|
||||||
ENV_TESTING,
|
ENV_PROD,
|
||||||
ENV_STAGING,
|
ENV_SET,
|
||||||
ENV_PROD,
|
INTEGRATION_HEROKU,
|
||||||
ENV_SET,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_NETLIFY,
|
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,
|
||||||
EVENT_PUSH_SECRETS,
|
INTEGRATION_GITHUB_API_URL,
|
||||||
EVENT_PULL_SECRETS,
|
EVENT_PUSH_SECRETS,
|
||||||
ACTION_PUSH_TO_HEROKU,
|
EVENT_PULL_SECRETS,
|
||||||
INTEGRATION_OPTIONS
|
INTEGRATION_OPTIONS,
|
||||||
};
|
SMTP_HOST_SENDGRID,
|
||||||
|
SMTP_HOST_MAILGUN,
|
||||||
|
PLAN_STARTER,
|
||||||
|
PLAN_PRO,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CLIENT_ID_HEROKU,
|
CLIENT_ID_HEROKU,
|
||||||
CLIENT_ID_NETLIFY,
|
CLIENT_ID_NETLIFY,
|
||||||
|
CLIENT_ID_GITHUB,
|
||||||
CLIENT_SLUG_VERCEL
|
CLIENT_SLUG_VERCEL
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
@@ -8,10 +9,12 @@ import {
|
|||||||
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
|
||||||
@@ -19,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 = [
|
||||||
{
|
{
|
||||||
@@ -56,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',
|
||||||
@@ -104,16 +121,19 @@ const INTEGRATION_OPTIONS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export {
|
export {
|
||||||
INTEGRATION_HEROKU,
|
INTEGRATION_HEROKU,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_OAUTH2,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_OAUTH2,
|
||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_OPTIONS
|
INTEGRATION_VERCEL_API_URL,
|
||||||
}
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
|
INTEGRATION_GITHUB_API_URL,
|
||||||
|
INTEGRATION_OPTIONS
|
||||||
|
};
|
||||||
|
|||||||
7
backend/src/variables/smtp.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net';
|
||||||
|
const SMTP_HOST_MAILGUN = 'smtp.mailgun.org';
|
||||||
|
|
||||||
|
export {
|
||||||
|
SMTP_HOST_SENDGRID,
|
||||||
|
SMTP_HOST_MAILGUN
|
||||||
|
}
|
||||||
21
cli/go.mod
@@ -3,24 +3,37 @@ module github.com/Infisical/infisical-merge
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/99designs/keyring v1.2.2
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
golang.org/x/crypto v0.3.0
|
golang.org/x/crypto v0.3.0
|
||||||
|
golang.org/x/term v0.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alessio/shellescape v1.4.1 // indirect
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||||
github.com/chzyer/readline v1.5.1 // indirect
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||||
|
github.com/go-openapi/errors v0.20.2 // indirect
|
||||||
|
github.com/go-openapi/strfmt v0.21.3 // indirect
|
||||||
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||||
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||||
golang.org/x/net v0.2.0 // indirect
|
golang.org/x/net v0.2.0 // indirect
|
||||||
golang.org/x/sys v0.2.0 // indirect
|
golang.org/x/sys v0.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
|
github.com/jedib0t/go-pretty v4.3.0+incompatible
|
||||||
github.com/manifoldco/promptui v0.9.0
|
github.com/manifoldco/promptui v0.9.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/zalando/go-keyring v0.2.1
|
|
||||||
)
|
)
|
||||||
|
|||||||
88
cli/go.sum
@@ -1,5 +1,9 @@
|
|||||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
|
||||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
|
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||||
|
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
@@ -10,23 +14,57 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
|||||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
|
||||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
|
||||||
|
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||||
|
github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
|
||||||
|
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||||
|
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
|
||||||
|
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
|
||||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
|
||||||
|
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||||
|
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||||
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||||
|
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
@@ -34,32 +72,52 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
|||||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||||
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
|
||||||
|
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Short: "Infisical CLI is used to inject environment variables into any process",
|
Short: "Infisical CLI is used to inject environment variables into any process",
|
||||||
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
||||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
||||||
Version: "0.1.11",
|
Version: "0.1.12",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
@@ -31,4 +31,8 @@ func init() {
|
|||||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
|
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
|
||||||
rootCmd.PersistentFlags().StringVar(&util.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend")
|
rootCmd.PersistentFlags().StringVar(&util.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend")
|
||||||
|
|
||||||
|
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||||
|
util.InitKeyRingInstance()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
cli/packages/cmd/vault.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||||
|
*/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/99designs/keyring"
|
||||||
|
"github.com/Infisical/infisical-merge/packages/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var vaultSetCmd = &cobra.Command{
|
||||||
|
Example: `infisical vault set pass`,
|
||||||
|
Use: "set [vault-name]",
|
||||||
|
Short: "Used to set the vault backend to store your login details securely at rest",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
PreRun: toggleDebug,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
wantedVaultTypeName := args[0]
|
||||||
|
currentVaultBackend, err := util.GetCurrentVaultBackend()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantedVaultTypeName == string(currentVaultBackend) {
|
||||||
|
log.Errorf("You are already on vault backend [%s]", currentVaultBackend)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isVaultToSwitchToValid(wantedVaultTypeName) {
|
||||||
|
configFile, err := util.GetConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile.VaultBackendType = keyring.BackendType(wantedVaultTypeName) // save selected vault
|
||||||
|
configFile.LoggedInUserEmail = "" // reset the logged in user to prompt them to re login
|
||||||
|
|
||||||
|
err = util.WriteConfigFile(&configFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Successfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]", currentVaultBackend, wantedVaultTypeName)
|
||||||
|
} else {
|
||||||
|
log.Errorf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, keyring.AvailableBackends())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// runCmd represents the run command
|
||||||
|
var vaultCmd = &cobra.Command{
|
||||||
|
Use: "vault",
|
||||||
|
Short: "Used to manage where your Infisical login token is saved on your machine",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
PreRun: toggleDebug,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
printAvailableVaultBackends()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func printAvailableVaultBackends() {
|
||||||
|
log.Infof("The following vaults are available on your system:")
|
||||||
|
for _, backend := range keyring.AvailableBackends() {
|
||||||
|
log.Infof("- %s", backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVaultBackend, err := util.GetCurrentVaultBackend()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("\nYou are currently using [%s] vault to store your login credentials", string(currentVaultBackend))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the vault that the user wants to switch to is a valid available vault
|
||||||
|
func isVaultToSwitchToValid(vaultNameToSwitchTo string) bool {
|
||||||
|
isFound := false
|
||||||
|
for _, backend := range keyring.AvailableBackends() {
|
||||||
|
if vaultNameToSwitchTo == string(backend) {
|
||||||
|
isFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
vaultCmd.AddCommand(vaultSetCmd)
|
||||||
|
rootCmd.AddCommand(vaultCmd)
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import "github.com/99designs/keyring"
|
||||||
|
|
||||||
type UserCredentials struct {
|
type UserCredentials struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
PrivateKey string `json:"privateKey"`
|
PrivateKey string `json:"privateKey"`
|
||||||
@@ -8,7 +10,8 @@ type UserCredentials struct {
|
|||||||
|
|
||||||
// The file struct for Infisical config file
|
// The file struct for Infisical config file
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
LoggedInUserEmail string `json:"loggedInUserEmail"`
|
LoggedInUserEmail string `json:"loggedInUserEmail"`
|
||||||
|
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SingleEnvironmentVariable struct {
|
type SingleEnvironmentVariable struct {
|
||||||
|
|||||||
@@ -24,8 +24,15 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get existing config
|
||||||
|
existingConfigFile, err := GetConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writeInitalConfig: unable to write config file because [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
configFile := models.ConfigFile{
|
configFile := models.ConfigFile{
|
||||||
LoggedInUserEmail: userCredentials.Email,
|
LoggedInUserEmail: userCredentials.Email,
|
||||||
|
VaultBackendType: existingConfigFile.VaultBackendType,
|
||||||
}
|
}
|
||||||
|
|
||||||
configFileMarshalled, err := json.Marshal(configFile)
|
configFileMarshalled, err := json.Marshal(configFile)
|
||||||
@@ -152,3 +159,50 @@ func GetAllWorkSpaceConfigsStartingFromCurrentPath() (workspaces []models.Worksp
|
|||||||
|
|
||||||
return listOfWorkSpaceConfigs, nil
|
return listOfWorkSpaceConfigs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the infisical config file and if it doesn't exist, return empty config model, otherwise raise error
|
||||||
|
func GetConfigFile() (models.ConfigFile, error) {
|
||||||
|
fullConfigFilePath, _, err := GetFullConfigFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return models.ConfigFile{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFileAsBytes, err := os.ReadFile(fullConfigFilePath)
|
||||||
|
if err != nil {
|
||||||
|
if err, ok := err.(*os.PathError); ok {
|
||||||
|
return models.ConfigFile{}, nil
|
||||||
|
} else {
|
||||||
|
return models.ConfigFile{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var configFile models.ConfigFile
|
||||||
|
err = json.Unmarshal(configFileAsBytes, &configFile)
|
||||||
|
if err != nil {
|
||||||
|
return models.ConfigFile{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a ConfigFile to disk. Raise error if unable to save the model to ask
|
||||||
|
func WriteConfigFile(configFile *models.ConfigFile) error {
|
||||||
|
fullConfigFilePath, _, err := GetFullConfigFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when getting config file path [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configFileMarshalled, err := json.Marshal(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when marshalling the config file [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file in directory
|
||||||
|
err = WriteToFile(fullConfigFilePath, configFileMarshalled, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when write the config to file [err=%s]", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ package util
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
|
"github.com/99designs/keyring"
|
||||||
"github.com/Infisical/infisical-merge/packages/models"
|
"github.com/Infisical/infisical-merge/packages/models"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/zalando/go-keyring"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const SERVICE_NAME = "infisical"
|
const SERVICE_NAME = "infisical"
|
||||||
@@ -17,26 +16,30 @@ const SERVICE_NAME = "infisical"
|
|||||||
func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
|
func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
|
||||||
userCredMarshalled, err := json.Marshal(userCred)
|
userCredMarshalled, err := json.Marshal(userCred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Something went wrong when marshalling user creds:", err)
|
return fmt.Errorf("StoreUserCredsInKeyRing: something went wrong when marshalling user creds [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = keyring.Set(SERVICE_NAME, userCred.Email, string(userCredMarshalled))
|
err = keyringInstance.Set(keyring.Item{
|
||||||
|
Key: userCred.Email,
|
||||||
|
Data: []byte(string(userCredMarshalled)),
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to store user credentials:", err)
|
return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) {
|
func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) {
|
||||||
credentialsString, err := keyring.Get(SERVICE_NAME, userEmail)
|
credentialsValue, err := keyringInstance.Get(userEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.UserCredentials{}, fmt.Errorf("Unable to get key from Keyring:", err)
|
return models.UserCredentials{}, fmt.Errorf("Unable to get key from Keyring. could not find login credentials in your Keyring. This is common if you have switched vault backend recently. If so, please login in again and retry:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var userCredentials models.UserCredentials
|
var userCredentials models.UserCredentials
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(credentialsString), &userCredentials)
|
err = json.Unmarshal([]byte(credentialsValue.Data), &userCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.UserCredentials{}, fmt.Errorf("Something went wrong when unmarshalling user creds:", err)
|
return models.UserCredentials{}, fmt.Errorf("Something went wrong when unmarshalling user creds:", err)
|
||||||
}
|
}
|
||||||
@@ -50,23 +53,13 @@ func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentia
|
|||||||
|
|
||||||
func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) {
|
func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) {
|
||||||
if ConfigFileExists() {
|
if ConfigFileExists() {
|
||||||
fullConfigFilePath, _, err := GetFullConfigFilePath()
|
configFile, err := GetConfigFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugln("Error gettting full path:", err)
|
return false, "", fmt.Errorf("IsUserLoggedIn: unable to get logged in user from config file [err=%s]", err)
|
||||||
return false, "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configFileAsBytes, err := os.ReadFile(fullConfigFilePath)
|
if configFile.LoggedInUserEmail == "" {
|
||||||
if err != nil {
|
return false, "", nil
|
||||||
log.Debugln("Unable to read config file:", err)
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var configFile models.ConfigFile
|
|
||||||
err = json.Unmarshal(configFileAsBytes, &configFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugln("Unable to unmarshal config file:", err)
|
|
||||||
return false, "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail)
|
userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail)
|
||||||
|
|||||||
71
cli/packages/util/vault.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/99designs/keyring"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keyring instance
|
||||||
|
var keyringInstance keyring.Keyring
|
||||||
|
var keyringInstanceConfig keyring.Config
|
||||||
|
|
||||||
|
func GetCurrentVaultBackend() (keyring.BackendType, error) {
|
||||||
|
configFile, err := GetConfigFile()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getCurrentVaultBackend: unable to get config file [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if configFile.VaultBackendType == "" {
|
||||||
|
if keyring.AvailableBackends()[0] == keyring.FileBackend {
|
||||||
|
}
|
||||||
|
return keyring.AvailableBackends()[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFile.VaultBackendType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitKeyRingInstance() {
|
||||||
|
currentVaultBackend, err := GetCurrentVaultBackend()
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("InitKeyRingInstance: unable to get the current vault backend, [err=%s]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyringInstanceConfig = keyring.Config{
|
||||||
|
FilePasswordFunc: fileKeyringPassphrasePrompt,
|
||||||
|
ServiceName: SERVICE_NAME,
|
||||||
|
LibSecretCollectionName: SERVICE_NAME,
|
||||||
|
KWalletAppID: SERVICE_NAME,
|
||||||
|
KWalletFolder: SERVICE_NAME,
|
||||||
|
KeychainTrustApplication: true,
|
||||||
|
WinCredPrefix: SERVICE_NAME,
|
||||||
|
FileDir: fmt.Sprintf("~/%s-file-vault", SERVICE_NAME),
|
||||||
|
KeychainAccessibleWhenUnlocked: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user explicitly sets a vault backend, then only use that
|
||||||
|
if currentVaultBackend != "" {
|
||||||
|
keyringInstanceConfig.AllowedBackends = []keyring.BackendType{keyring.BackendType(currentVaultBackend)}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyringInstance, err = keyring.Open(keyringInstanceConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("InitKeyRingInstance: Unable to create instance of Keyring because of [err=%s]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileKeyringPassphrasePrompt(prompt string) (string, error) {
|
||||||
|
if password, ok := os.LookupEnv("INFISICAL_VAULT_FILE_PASSPHRASE"); ok {
|
||||||
|
return password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: ", prompt)
|
||||||
|
b, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
35
cli/packages/visualize/visualize.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package visualize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jedib0t/go-pretty/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Given headers and rows, this function will print out a table
|
||||||
|
func Table(headers []string, rows [][]string) {
|
||||||
|
t := table.NewWriter()
|
||||||
|
t.SetOutputMirror(os.Stdout)
|
||||||
|
t.SetStyle(table.StyleLight)
|
||||||
|
|
||||||
|
// t.SetTitle("Title")
|
||||||
|
t.Style().Options.DrawBorder = true
|
||||||
|
t.Style().Options.SeparateHeader = true
|
||||||
|
t.Style().Options.SeparateColumns = true
|
||||||
|
|
||||||
|
tableHeaders := table.Row{}
|
||||||
|
for _, header := range headers {
|
||||||
|
tableHeaders = append(tableHeaders, header)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AppendHeader(tableHeaders)
|
||||||
|
for _, row := range rows {
|
||||||
|
tableRow := table.Row{}
|
||||||
|
for _, val := range row {
|
||||||
|
tableRow = append(tableRow, val)
|
||||||
|
}
|
||||||
|
t.AppendRow(tableRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Frequently Asked Questions'
|
|
||||||
description: 'Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g).'
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem with SMTP
|
|
||||||
|
|
||||||
If you opt for actual SMTP server (not the local MailHog), you have to have the right environment variables set.
|
|
||||||
|
|
||||||
You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems.
|
|
||||||
|
|
||||||
You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails.
|
|
||||||
|
|
||||||
If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/unable-to-send-email-in-c-sharp-less-secure-app-access-not-longer-available/72553362#72553362) should help.
|
|
||||||
|
|
||||||
## `MONGO_URL` issues
|
|
||||||
|
|
||||||
Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`.
|
|
||||||
@@ -16,59 +16,44 @@ 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. It's best to start with the provided [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) template containing the necessary envars to fill out your .env file — you only have to modify the SMTP parameters.
|
||||||
`.env.example` as an example guide.
|
|
||||||
|
|
||||||
Mandatory variables in the `.env` file:
|
<Warning>
|
||||||
|
The pre-populated environment variable values in the `.env.example` file are meant to be used in development only.
|
||||||
|
You'll want to fill in your own values in production, especially concerning encryption keys, secrets, and SMTP parameters.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
1. Keys and JWT variables
|
Refer to the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) for guidance on each envar.
|
||||||
|
|
||||||

|
### Helpful tips for developing with Infisical:
|
||||||
|
|
||||||
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).
|
<Tip>
|
||||||
|
Use the `ENCRYPTION_KEY`, JWT-secret envars, `MONGO_URL`, `MONGO_USERNAME`, `MONGO_PASSWORD` provided in the `.env.example` file.
|
||||||
|
|
||||||
For the `PRIVATE_KEY and PUBLIC_KEY` you can use the ones shown in the screenshot:
|
If setting your own values:
|
||||||
|
|
||||||
```
|
- `ENCRYPTION_KEY` should be a [32-byte random hex](https://www.browserling.com/tools/random-hex)
|
||||||
PRIVATE_KEY='oGVv5rThrpZ7WLgQW27chY1cXngr4wLQIZnGfSKgHPk='
|
- `MONGO_URL` should take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`.
|
||||||
PUBLIC_KEY='ldr6JaC7AY+tun3omGLdE4SWpkJbtVBOI54KfUP53Xc='
|
</Tip>
|
||||||
```
|
|
||||||
|
|
||||||
2. Mongo variables and site URL
|
<Tip>
|
||||||
|
Bring and configure your own SMTP server by following our [email configuration guide](https://infisical.com/docs/self-hosting/configuration/email) (we recommend using either SendGrid or Mailgun).
|
||||||
|
|
||||||

|
Alternatively, you can use the provided development (Mailhog) SMTP server to send 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` below.
|
||||||
|
</Tip>
|
||||||
These variables are used to connect the MongoDB and set the URL for the localhost.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
3. Mail SMTP service variables
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following.
|
|
||||||
|
|
||||||
For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification.
|
|
||||||
|
|
||||||
For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable.
|
|
||||||
|
|
||||||
Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
SMTP_HOST=smtp-server
|
SMTP_HOST=smtp-server
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
SMTP_NAME=<whatever you like>
|
SMTP_FROM_ADDRESS=team@infisical.com
|
||||||
|
SMTP_FROM_NAME=Infisical
|
||||||
SMTP_USERNAME=team@infisical.com
|
SMTP_USERNAME=team@infisical.com
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure to leave the `SMTP_PASSWORD` blank so the backend will be able to connect to MailHog
|
<Warning>
|
||||||
|
If using Mailhog, make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog.
|
||||||
You can browse `http://localhost:8025/` to browse email messages sent by the backend.
|
</Warning>
|
||||||
|
|
||||||
With these environment variables, you will be ready to run the docker-compose.
|
|
||||||
|
|
||||||
## Docker for development
|
## Docker for development
|
||||||
|
|
||||||
@@ -84,12 +69,4 @@ Then browse http://localhost:8080
|
|||||||
docker-compose -f docker-compose.dev.yml down
|
docker-compose -f docker-compose.dev.yml down
|
||||||
# start services
|
# start services
|
||||||
docker-compose -f docker-compose.dev.yml up
|
docker-compose -f docker-compose.dev.yml up
|
||||||
```
|
```
|
||||||
|
|
||||||
The docker-compose development environment consists of:
|
|
||||||
|
|
||||||
- nginx
|
|
||||||
- frontend
|
|
||||||
- backend
|
|
||||||
- mongo
|
|
||||||
- mongo-express
|
|
||||||
@@ -8,7 +8,7 @@ Note that the Infisical CLI is platform-agnostic and can inject environment vari
|
|||||||
|
|
||||||
## Set up Infisical Cloud
|
## Set up Infisical Cloud
|
||||||
|
|
||||||
1. Login or create an accout at `app.infisical.com`.
|
1. Login or create an account at `app.infisical.com`.
|
||||||
2. Create a new project.
|
2. Create a new project.
|
||||||
3. Populate your environment variables as in the image below.
|
3. Populate your environment variables as in the image below.
|
||||||
|
|
||||||
|
|||||||
BIN
docs/images/email-mailhog-credentials.png
Normal file
|
After Width: | Height: | Size: 429 KiB |
BIN
docs/images/email-sendgrid-create-key.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/images/email-sendgrid-restrictions.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/images/integrations-github-auth.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/integrations-github.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.3 MiB |
34
docs/integrations/cicd/githubactions.mdx
Normal 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
|
||||||
|
|
||||||
|

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

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

|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
|
|||||||
| [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 |
|
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
|
||||||
| [Netlify](/integrations/cloud/netlify) | 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 |
|
||||||
|
|||||||
@@ -102,18 +102,21 @@
|
|||||||
{
|
{
|
||||||
"group": "Self-hosting",
|
"group": "Self-hosting",
|
||||||
"pages": [
|
"pages": [
|
||||||
"self-hosting/overview",
|
"self-hosting/overview"
|
||||||
{
|
]
|
||||||
"group": "Deployments options",
|
},
|
||||||
"pages": [
|
{
|
||||||
"self-hosting/deployments/linux",
|
"group": "Deployment options",
|
||||||
"self-hosting/deployments/kubernetes"
|
"pages": [
|
||||||
]
|
"self-hosting/deployments/linux",
|
||||||
},
|
"self-hosting/deployments/kubernetes"
|
||||||
{
|
]
|
||||||
"group": "Configuration",
|
},
|
||||||
"pages": ["self-hosting/configuration/envars"]
|
{
|
||||||
}
|
"group": "Configuration",
|
||||||
|
"pages": [
|
||||||
|
"self-hosting/configuration/envars",
|
||||||
|
"self-hosting/configuration/email"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -140,7 +143,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "CI/CD",
|
"group": "CI/CD",
|
||||||
"pages": ["integrations/cicd/circleci"]
|
"pages": [
|
||||||
|
"integrations/cicd/githubactions",
|
||||||
|
"integrations/cicd/circleci"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Frameworks",
|
"group": "Frameworks",
|
||||||
@@ -174,8 +180,7 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"contributing/overview",
|
"contributing/overview",
|
||||||
"contributing/code-of-conduct",
|
"contributing/code-of-conduct",
|
||||||
"contributing/developing",
|
"contributing/developing"
|
||||||
"contributing/FAQ"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
75
docs/self-hosting/configuration/email.mdx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
title: "Email"
|
||||||
|
description: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical requires you to configure your own SMTP server for certain functionality like:
|
||||||
|
|
||||||
|
- Sending email confirmation links to sign up.
|
||||||
|
- Sending invite links for projects.
|
||||||
|
- Sending alerts.
|
||||||
|
|
||||||
|
We strongly recommend using an email service to act as your email server and provide examples for common providers.
|
||||||
|
|
||||||
|
## General configuration
|
||||||
|
|
||||||
|
By default, you need to configure the following SMTP [environment variables](https://infisical.com/docs/self-hosting/configuration/envars):
|
||||||
|
|
||||||
|
- `SMTP_HOST`: Hostname to connect to for establishing SMTP connections.
|
||||||
|
- `SMTP_USERNAME`: Credential to connect to host (e.g. team@infisical.com)
|
||||||
|
- `SMTP_PASSWORD`: Credential to connect to host.
|
||||||
|
- `SMTP_PORT`: Port to connect to for establishing SMTP connections.
|
||||||
|
- `SMTP_SECURE`: If `true`, the connection will use TLS when connecting to server with special configs for SendGrid and Mailgun. If `false` (the default) then TLS is used if server supports the STARTTLS extension.
|
||||||
|
- `SMTP_FROM_ADDRESS`: Email address to be used for sending emails (e.g. team@infisical.com).
|
||||||
|
- `SMTP_FROM_NAME`: Name label to be used in `From` field (e.g. Team).
|
||||||
|
|
||||||
|
Below you will find details on how to configure common email providers (not in any particular order).
|
||||||
|
|
||||||
|
## Twilio SendGrid
|
||||||
|
|
||||||
|
1. Create an account and configure [SendGrid](https://sendgrid.com) to send emails.
|
||||||
|
2. Create a SendGrid API Key under Settings > [API Keys](https://app.sendgrid.com/settings/api_keys)
|
||||||
|
3. Set a name for your API Key, we recommend using "Infisical," and select the "Restricted Key" option. You will need to enable the "Mail Send" permission as shown below:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. With the API Key, you can now set your SMTP environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
SMTP_HOST=smtp.sendgrid.net
|
||||||
|
SMTP_USERNAME=apikey
|
||||||
|
SMTP_PASSWORD=SG.rqFsfjxYPiqE1lqZTgD_lz7x8IVLx # your SendGrid API Key from step above
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_SECURE=true
|
||||||
|
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||||
|
SMTP_FROM_NAME=Infisical
|
||||||
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Remember that you will need to restart Infisical for this to work properly.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Mailgun
|
||||||
|
|
||||||
|
1. Create an account and configure [Mailgun](https://www.mailgun.com) to send emails.
|
||||||
|
2. Obtain your Mailgun credentials in Sending > Overview > SMTP
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. With your Mailgun credentials, you can now set up your SMTP environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
SMTP_HOST=smtp.mailgun.org # obtained from credentials page
|
||||||
|
SMTP_USERNAME=postmaster@example.mailgun.org # obtained from credentials page
|
||||||
|
SMTP_PASSWORD=password # obtained from credentials page
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_SECURE=true
|
||||||
|
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
|
||||||
|
SMTP_FROM_NAME=Infisical
|
||||||
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Remember that you will need to restart Infisical for this to work properly.
|
||||||
|
</Info>
|
||||||
@@ -3,18 +3,15 @@ title: "Environment Variables"
|
|||||||
description: ""
|
description: ""
|
||||||
---
|
---
|
||||||
|
|
||||||
## The .env file
|
Configuring Infisical requires setting some environment variables. There is a file called [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) at the root directory of our main repo that you can use to create a `.env` file before you start the server.
|
||||||
|
|
||||||
Configuring Infisical requires setting some environment variables. There is a file called `.env.example` at the root directory of our main repo that you can use to create a `.env` before you start the server.
|
|
||||||
|
|
||||||
| 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` |
|
||||||
@@ -23,14 +20,21 @@ Configuring Infisical requires setting some environment variables. There is a fi
|
|||||||
| `MONGO_USERNAME` | MongoDB username if using container | `None` |
|
| `MONGO_USERNAME` | MongoDB username if using container | `None` |
|
||||||
| `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 | `None` |
|
||||||
| `SMTP_NAME` | Name label to be used in From field (e.g. `Team`) | `None` |
|
|
||||||
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
|
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
|
||||||
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
||||||
|
| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
|
||||||
|
| `SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
|
||||||
|
| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `team@infisical.com`) | `None` |
|
||||||
|
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
|
||||||
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
|
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
|
||||||
| `CLIENT_ID_VERCEL` | OAuth client id for Vercel integration | `None` |
|
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
|
||||||
| `CLIENT_ID_NETLIFY` | OAuth client id for Netlify integration | `None` |
|
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
|
||||||
| `CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` |
|
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
|
||||||
| `CLIENT_SECRET_VERCEL` | OAuth client secret for Vercel integration | `None` |
|
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
|
||||||
| `CLIENT_SECRET_NETLIFY` | OAuth client secret for Netlify integration | `None` |
|
| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` |
|
||||||
|
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` |
|
||||||
|
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` |
|
||||||
|
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` |
|
||||||
|
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` |
|
||||||
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
|
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ that by adding the `--namespace <namespace-to-install-to>` to your `helm install
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
## Installs to default namespace
|
## Installs to default namespace
|
||||||
helm install infisical-helm-charts/infisical --values <path to the values.yaml you downloaded/created in step 2>
|
helm install infisical-helm-charts/infisical --generate-name --values <path to the values.yaml you downloaded/created in step 2>
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
@@ -50,5 +50,4 @@ If you have not filled out all of the required environment variables, you will s
|
|||||||
do so.
|
do so.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
#### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
||||||
Note: Please allow an additional time (2 minutes) for the frontend pods to be fully ready.
|
|
||||||
@@ -209,7 +209,7 @@ const AddServiceTokenDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36">
|
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-44">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={serviceToken}
|
value={serviceToken}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ const Integration = ({
|
|||||||
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
|
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
|
||||||
siteId
|
siteId
|
||||||
});
|
});
|
||||||
|
|
||||||
router.reload();
|
router.reload();
|
||||||
}}
|
}}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
|
|||||||
28
frontend/pages/404.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function Custom404() {
|
||||||
|
return (
|
||||||
|
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
|
||||||
|
<Head>
|
||||||
|
<title>Infisical | Page Not Found</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
</Head>
|
||||||
|
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
|
||||||
|
<p className="text-4xl mt-32">Oops, something went wrong</p>
|
||||||
|
<p className="mt-2 mb-1 text-lg">Think this is a mistake? Email <a className="text-primary underline underline-offset-4" href="mailto:team@infisical.com">team@infisical.com</a> and we`ll fix it! </p>
|
||||||
|
<Link href="/dashboard" className="bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold">
|
||||||
|
Go to Dashboard
|
||||||
|
</Link>
|
||||||
|
<Image
|
||||||
|
src="/images/dragon-404.svg"
|
||||||
|
height={554}
|
||||||
|
width={942}
|
||||||
|
alt="google logo"
|
||||||
|
></Image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
frontend/pages/github.js
Normal 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;
|
||||||
@@ -123,6 +123,8 @@ export default function Integrations() {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const handleIntegrationOption = async ({ integrationOption }) => {
|
const handleIntegrationOption = async ({ integrationOption }) => {
|
||||||
|
|
||||||
|
console.log('handleIntegrationOption', integrationOption);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||||
@@ -139,6 +141,9 @@ export default function Integrations() {
|
|||||||
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);
|
||||||
|
|||||||
@@ -1,21 +1,30 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
|
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
export default function NoProjects() {
|
export default function NoProjects() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
||||||
<FontAwesomeIcon
|
<div
|
||||||
icon={faFolderOpen}
|
className="mb-4 mr-16"
|
||||||
className="text-7xl mb-8 w-full px-auto"
|
>
|
||||||
/>
|
<Image
|
||||||
<div className="max-w-md">
|
src="/images/dragon-cant-find.png"
|
||||||
You are not part of any projects in this organization yet. When you do,
|
height={270}
|
||||||
they will appear here.
|
width={436}
|
||||||
|
alt="google logo"
|
||||||
|
></Image>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-md mt-4">
|
<div className="p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl">
|
||||||
Create a new project, or ask other organization members to give you
|
<div className="max-w-md">
|
||||||
neccessary permissions.
|
You are not part of any projects in this organization yet. When you do,
|
||||||
|
they will appear here.
|
||||||
|
</div>
|
||||||
|
<div className="max-w-md mt-4">
|
||||||
|
Create a new project, or ask other organization members to give you
|
||||||
|
neccessary permissions.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ const props = {
|
|||||||
backgroundColor: '#0d1117',
|
backgroundColor: '#0d1117',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: '1px solid gray',
|
border: '1px solid gray',
|
||||||
textAlign: 'center'
|
textAlign: 'center',
|
||||||
}
|
},
|
||||||
} as const;
|
} as const;
|
||||||
const propsPhone = {
|
const propsPhone = {
|
||||||
inputStyle: {
|
inputStyle: {
|
||||||
@@ -56,8 +56,8 @@ const propsPhone = {
|
|||||||
backgroundColor: '#0d1117',
|
backgroundColor: '#0d1117',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: '1px solid gray',
|
border: '1px solid gray',
|
||||||
textAlign: 'center'
|
textAlign: 'center',
|
||||||
}
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default function SignUp() {
|
export default function SignUp() {
|
||||||
@@ -81,6 +81,7 @@ export default function SignUp() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [errorLogin, setErrorLogin] = useState(false);
|
const [errorLogin, setErrorLogin] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
|
||||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||||
const [verificationToken, setVerificationToken] = useState('');
|
const [verificationToken, setVerificationToken] = useState('');
|
||||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||||
@@ -148,7 +149,8 @@ export default function SignUp() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the criteria.
|
// Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the
|
||||||
|
// criteria.
|
||||||
const signupErrorCheck = async () => {
|
const signupErrorCheck = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
let errorCheck = false;
|
let errorCheck = false;
|
||||||
@@ -169,7 +171,7 @@ export default function SignUp() {
|
|||||||
setPasswordErrorLength,
|
setPasswordErrorLength,
|
||||||
setPasswordErrorNumber,
|
setPasswordErrorNumber,
|
||||||
setPasswordErrorLowerCase,
|
setPasswordErrorLowerCase,
|
||||||
currentErrorCheck: errorCheck
|
currentErrorCheck: errorCheck,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!errorCheck) {
|
if (!errorCheck) {
|
||||||
@@ -186,8 +188,8 @@ export default function SignUp() {
|
|||||||
.slice(0, 32)
|
.slice(0, 32)
|
||||||
.padStart(
|
.padStart(
|
||||||
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
||||||
'0'
|
'0',
|
||||||
)
|
),
|
||||||
}) as { ciphertext: string; iv: string; tag: string };
|
}) as { ciphertext: string; iv: string; tag: string };
|
||||||
|
|
||||||
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
|
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
|
||||||
@@ -195,7 +197,7 @@ export default function SignUp() {
|
|||||||
client.init(
|
client.init(
|
||||||
{
|
{
|
||||||
username: email,
|
username: email,
|
||||||
password: password
|
password: password,
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
client.createVerifier(
|
client.createVerifier(
|
||||||
@@ -204,14 +206,14 @@ export default function SignUp() {
|
|||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
organizationName: firstName + "'s organization",
|
organizationName: firstName + '\'s organization',
|
||||||
publicKey: PUBLIC_KEY,
|
publicKey: PUBLIC_KEY,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt: result.salt,
|
salt: result.salt,
|
||||||
verifier: result.verifier,
|
verifier: result.verifier,
|
||||||
token: verificationToken
|
token: verificationToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
// if everything works, go the main dashboard page.
|
// if everything works, go the main dashboard page.
|
||||||
@@ -230,27 +232,37 @@ export default function SignUp() {
|
|||||||
setErrorLogin,
|
setErrorLogin,
|
||||||
router,
|
router,
|
||||||
true,
|
true,
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
incrementStep();
|
incrementStep();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resendVerificationEmail = async () => {
|
||||||
|
setIsResendingVerificationEmail(true);
|
||||||
|
setIsLoading(true);
|
||||||
|
await sendVerificationEmail(email);
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsResendingVerificationEmail(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
// Step 1 of the sign up process (enter the email or choose google authentication)
|
// Step 1 of the sign up process (enter the email or choose google authentication)
|
||||||
const step1 = (
|
const step1 = (
|
||||||
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 mb-48 md:mb-16 rounded-xl drop-shadow-xl">
|
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 mb-48 md:mb-16 rounded-xl drop-shadow-xl">
|
||||||
<p className="text-4xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
<p className="text-4xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||||
{"Let'"}s get started
|
{'Let\''}s get started
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-h-24 max-w-md mx-auto pt-2">
|
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-h-24 max-w-md mx-auto pt-2">
|
||||||
<Link href="/login">
|
<Link href="/login">
|
||||||
@@ -284,7 +296,7 @@ export default function SignUp() {
|
|||||||
acknowledged the Privacy Policy.
|
acknowledged the Privacy Policy.
|
||||||
</p>
|
</p>
|
||||||
<div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg">
|
<div className="text-l mt-6 m-2 md:m-8 px-8 py-1 text-lg">
|
||||||
<Button text="Get Started" onButtonPressed={emailCheck} size="lg" />
|
<Button loading={isLoading} text="Get Started" onButtonPressed={emailCheck} size="lg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,7 +306,7 @@ export default function SignUp() {
|
|||||||
const step2 = (
|
const step2 = (
|
||||||
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
|
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
|
||||||
<p className="text-l flex justify-center text-gray-400">
|
<p className="text-l flex justify-center text-gray-400">
|
||||||
{"We've"} sent a verification email to{' '}
|
{'We\'ve'} sent a verification email to{' '}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-l flex justify-center font-semibold my-2 text-gray-400">
|
<p className="text-l flex justify-center font-semibold my-2 text-gray-400">
|
||||||
{email}{' '}
|
{email}{' '}
|
||||||
@@ -328,13 +340,16 @@ export default function SignUp() {
|
|||||||
<Button text="Verify" onButtonPressed={incrementStep} size="lg" />
|
<Button text="Verify" onButtonPressed={incrementStep} size="lg" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
|
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
|
||||||
{/* <Link href="/login">
|
<div className="flex flex-row items-baseline gap-1">
|
||||||
<button className="w-full hover:opacity-90 duration-200">
|
<span className="text-gray-400">
|
||||||
<u className="font-normal text-sm text-sky-700">
|
Not seeing an email?
|
||||||
Not seeing an email? Resend
|
</span>
|
||||||
</u>
|
<u className={`font-normal text-sm ${isResendingVerificationEmail ? 'text-gray-400' : 'text-sky-500 hover:opacity-90 duration-200'}`}>
|
||||||
</button>
|
<button disabled={isLoading} onClick={resendVerificationEmail}>
|
||||||
</Link> */}
|
{isResendingVerificationEmail ? 'Resending...' : 'Resend'}
|
||||||
|
</button>
|
||||||
|
</u>
|
||||||
|
</div>
|
||||||
<p className="text-sm text-gray-500 pb-2">
|
<p className="text-sm text-gray-500 pb-2">
|
||||||
Make sure to check your spam inbox.
|
Make sure to check your spam inbox.
|
||||||
</p>
|
</p>
|
||||||
@@ -382,7 +397,7 @@ export default function SignUp() {
|
|||||||
setPasswordErrorLength,
|
setPasswordErrorLength,
|
||||||
setPasswordErrorNumber,
|
setPasswordErrorNumber,
|
||||||
setPasswordErrorLowerCase,
|
setPasswordErrorLowerCase,
|
||||||
currentErrorCheck: false
|
currentErrorCheck: false,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
type="password"
|
type="password"
|
||||||
@@ -506,7 +521,7 @@ export default function SignUp() {
|
|||||||
password,
|
password,
|
||||||
personalName: firstName + ' ' + lastName,
|
personalName: firstName + ' ' + lastName,
|
||||||
setBackupKeyError,
|
setBackupKeyError,
|
||||||
setBackupKeyIssued
|
setBackupKeyIssued,
|
||||||
});
|
});
|
||||||
const userWorkspaces = await getWorkspaces();
|
const userWorkspaces = await getWorkspaces();
|
||||||
const userWorkspace = userWorkspaces[0]._id;
|
const userWorkspace = userWorkspaces[0]._id;
|
||||||
|
|||||||
475
frontend/public/images/dragon-404.svg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
frontend/public/images/dragon-cant-find.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
frontend/public/images/integrations/GitHub.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
@@ -7,7 +7,7 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.1.5
|
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
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 4000
|
- containerPort: 3000
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ backend:
|
|||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
annotations: {}
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: "nginx"
|
||||||
hostName: example.com
|
hostName: example.com
|
||||||
frontend:
|
frontend:
|
||||||
path: /
|
path: /
|
||||||
|
|||||||