Merge branch 'main' into feat/error-handling

This commit is contained in:
BlackMagiq
2022-12-24 20:34:55 -05:00
committed by GitHub
60 changed files with 13520 additions and 969 deletions

View File

@@ -1,15 +1,13 @@
# Keys
# Required keys for platform encryption/decryption ops
PRIVATE_KEY=replace_with_nacl_sk
PUBLIC_KEY=replace_with_nacl_pk
ENCRYPTION_KEY=replace_with_lengthy_secure_hex
# Required key for platform encryption/decryption ops
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# JWT
# Required secrets to sign JWT tokens
JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex
JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex
JWT_AUTH_SECRET=replace_with_lengthy_secure_hex
JWT_SERVICE_SECRET=replace_with_lengthy_secure_hex
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
# JWT lifetime
# Optional lifetimes for JWT tokens expressed in seconds or a string
@@ -33,26 +31,28 @@ MONGO_PASSWORD=example
# Website URL
# Required
SITE_URL=http://localhost:8080
# Mail/SMTP
# Required to send emails
# By default, SMTP_HOST is set to smtp.gmail.com
SMTP_HOST=smtp.gmail.com
SMTP_HOST= # required
SMTP_USERNAME= # required
SMTP_PASSWORD= # required
SMTP_PORT=587
SMTP_NAME=Team
SMTP_USERNAME=team@infisical.com
SMTP_PASSWORD=
SMTP_SECURE=false
SMTP_FROM_ADDRESS= # required
SMTP_FROM_NAME=Infisical
# Integration
# Optional only if integration is used
CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors
SENTRY_DSN=

View File

@@ -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 }}

View File

@@ -146,7 +146,9 @@ We're currently setting the foundation and building [integrations](https://infis
🔜 AWS
</td>
<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 align="left" valign="middle">
🔜 Railway
@@ -179,7 +181,9 @@ We're currently setting the foundation and building [integrations](https://infis
🔜 TravisCI
</td>
<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 align="left" valign="middle">
🔜 Railway
@@ -317,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of
<!-- prettier-ignore-start -->
<!-- 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>

View File

@@ -24,8 +24,6 @@ declare global {
CLIENT_SECRET_NETLIFY: string;
POSTHOG_HOST: string;
POSTHOG_PROJECT_API_KEY: string;
PRIVATE_KEY: string;
PUBLIC_KEY: string;
SENTRY_DSN: string;
SITE_URL: string;
SMTP_HOST: string;

11457
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,11 @@
{
"dependencies": {
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"axios": "^1.1.3",
"bigint-conversion": "^2.2.2",
"cookie-parser": "^1.4.6",
@@ -15,11 +17,12 @@
"express-validator": "^6.14.2",
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.1.0",
"posthog-node": "^2.2.2",
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",

View File

@@ -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_VERCEL = process.env.CLIENT_ID_VERCEL!;
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_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!;
const CLIENT_SLUG_VERCEL= process.env.CLIENT_SLUG_VERCEL!;
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_PROJECT_API_KEY =
process.env.POSTHOG_PROJECT_API_KEY! ||
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
const PUBLIC_KEY = process.env.PUBLIC_KEY!;
const SENTRY_DSN = process.env.SENTRY_DSN!;
const SITE_URL = process.env.SITE_URL!;
const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com';
const SMTP_PORT = process.env.SMTP_PORT! || 587;
const SMTP_NAME = process.env.SMTP_NAME!;
const SMTP_HOST = process.env.SMTP_HOST!;
const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false;
const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587;
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
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_PRO = process.env.STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
@@ -58,21 +60,23 @@ export {
CLIENT_ID_HEROKU,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB,
CLIENT_SLUG_VERCEL,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,
PRIVATE_KEY,
PUBLIC_KEY,
SENTRY_DSN,
SITE_URL,
SMTP_HOST,
SMTP_PORT,
SMTP_NAME,
SMTP_SECURE,
SMTP_USERNAME,
SMTP_PASSWORD,
SMTP_FROM_ADDRESS,
SMTP_FROM_NAME,
STRIPE_PRODUCT_CARD_AUTH,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,

View File

@@ -2,7 +2,6 @@ import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Key } from '../models';
import { findMembership } from '../helpers/membership';
import { PUBLIC_KEY } from '../config';
import { GRANTED } from '../variables';
/**
@@ -84,16 +83,4 @@ export const getLatestKey = async (req: Request, res: Response) => {
}
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
});
};
};

View File

@@ -58,7 +58,8 @@ export const createServiceToken = async (req: Request, res: Response) => {
token = createToken({
payload: {
serviceTokenId: serviceToken._id.toString()
serviceTokenId: serviceToken._id.toString(),
workspaceId
},
expiresIn: expiresIn,
secret: JWT_SERVICE_SECRET

View File

@@ -2,7 +2,7 @@ import fs from 'fs';
import path from 'path';
import handlebars from 'handlebars';
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';
let smtpTransporter: nodemailer.Transporter;
@@ -34,7 +34,7 @@ const sendMail = async ({
const htmlToSend = temp(substitutions);
await smtpTransporter.sendMail({
from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`,
from: `"${SMTP_FROM_NAME}" <${SMTP_FROM_ADDRESS}>`,
to: recipients.join(', '),
subject: subjectLine,
html: htmlToSend

View File

@@ -1,17 +1,22 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import { Octokit } from '@octokit/rest';
import { IIntegrationAuth } from '../models';
import {
IIntegrationAuth
} from '../models';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL
} from '../variables';
interface GitHubApp {
name: string;
}
/**
* Return list of names of apps for integration named [integration]
* @param {Object} obj
@@ -21,47 +26,51 @@ import {
* @returns {String} apps.name - name of integration app
*/
const getApps = async ({
integrationAuth,
accessToken
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
interface App {
name: string;
siteId?: string;
}
interface App {
name: string;
siteId?: string;
}
let apps: App[]; // TODO: add type and define payloads for apps
try {
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
apps = await getAppsHeroku({
accessToken
});
break;
case INTEGRATION_VERCEL:
apps = await getAppsVercel({
accessToken
});
break;
case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({
integrationAuth,
accessToken
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get integration apps');
let apps: App[]; // TODO: add type and define payloads for apps
try {
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
apps = await getAppsHeroku({
accessToken
});
break;
case INTEGRATION_VERCEL:
apps = await getAppsVercel({
accessToken
});
break;
case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({
integrationAuth,
accessToken
});
break;
case INTEGRATION_GITHUB:
apps = await getAppsGithub({
integrationAuth,
accessToken
});
break;
}
return apps;
}
} catch (err) {
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
@@ -70,31 +79,29 @@ const getApps = async ({
* @returns {Object[]} apps - names of Heroku apps
* @returns {String} apps.name - name of Heroku app
*/
const getAppsHeroku = async ({
accessToken
}: {
accessToken: string;
}) => {
let apps;
try {
const res = (await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}`
}
})).data;
apps = res.map((a: any) => ({
name: a.name
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Heroku integration apps');
}
return apps;
}
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
let apps;
try {
const res = (
await axios.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}`
}
})
).data;
apps = res.map((a: any) => ({
name: a.name
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Heroku integration apps');
}
return apps;
};
/**
* Return list of names of apps for Vercel integration
@@ -103,30 +110,28 @@ const getAppsHeroku = async ({
* @returns {Object[]} apps - names of Vercel apps
* @returns {String} apps.name - name of Vercel app
*/
const getAppsVercel = async ({
accessToken
}: {
accessToken: string;
}) => {
let apps;
try {
const res = (await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})).data;
apps = res.projects.map((a: any) => ({
name: a.name
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Vercel integration apps');
}
return apps;
}
const getAppsVercel = async ({ accessToken }: { accessToken: string }) => {
let apps;
try {
const res = (
await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
).data;
apps = res.projects.map((a: any) => ({
name: a.name
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Vercel integration apps');
}
return apps;
};
/**
* Return list of names of sites for Netlify integration
@@ -136,34 +141,73 @@ const getAppsVercel = async ({
* @returns {String} apps.name - name of Netlify site
*/
const getAppsNetlify = async ({
integrationAuth,
accessToken
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let apps;
try {
const res = (await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})).data;
apps = res.map((a: any) => ({
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;
}
let apps;
try {
const res = (
await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
).data;
export {
getApps
}
apps = res.map((a: any) => ({
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 };

View File

@@ -1,46 +1,57 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
ACTION_PUSH_TO_HEROKU
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITHUB_API_URL
} from '../variables';
import {
SITE_URL,
CLIENT_SECRET_HEROKU,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY
import {
SITE_URL,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB
} from '../config';
import { user } from '../routes';
interface ExchangeCodeHerokuResponse {
token_type: string;
access_token: string;
expires_in: number;
refresh_token: string;
user_id: string;
session_nonce?: string;
token_type: string;
access_token: string;
expires_in: number;
refresh_token: string;
user_id: string;
session_nonce?: string;
}
interface ExchangeCodeVercelResponse {
token_type: string;
access_token: string;
installation_id: string;
user_id: string;
team_id?: string;
token_type: string;
access_token: string;
installation_id: string;
user_id: string;
team_id?: string;
}
interface ExchangeCodeNetlifyResponse {
access_token: string;
token_type: string;
refresh_token: string;
scope: string;
created_at: number;
access_token: string;
token_type: string;
refresh_token: string;
scope: string;
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
*/
const exchangeCode = async ({
integration,
code
}: {
integration: string;
code: string;
integration,
code
}: {
integration: string;
code: string;
}) => {
let obj = {} as any;
try {
switch (integration) {
case INTEGRATION_HEROKU:
obj = await exchangeCodeHeroku({
code
});
break;
case INTEGRATION_VERCEL:
obj = await exchangeCodeVercel({
code
});
break;
case INTEGRATION_NETLIFY:
obj = await exchangeCodeNetlify({
code
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange');
let obj = {} as any;
try {
switch (integration) {
case INTEGRATION_HEROKU:
obj = await exchangeCodeHeroku({
code
});
break;
case INTEGRATION_VERCEL:
obj = await exchangeCodeVercel({
code
});
break;
case INTEGRATION_NETLIFY:
obj = await exchangeCodeNetlify({
code
});
break;
case INTEGRATION_GITHUB:
obj = await exchangeCodeGithub({
code
});
break;
}
return obj;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange');
}
return obj;
};
/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
@@ -144,35 +160,33 @@ const exchangeCodeHeroku = async ({
* @returns {String} obj2.refreshToken - refresh token for Heroku API
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeVercel = async ({
code
}: {
code: string;
}) => {
let res: ExchangeCodeVercelResponse;
try {
res = (await axios.post(
INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({
code: code,
client_id: CLIENT_ID_VERCEL,
client_secret: CLIENT_SECRET_VERCEL,
redirect_uri: `${SITE_URL}/vercel`
} as any)
)).data;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Vercel');
}
return ({
accessToken: res.access_token,
refreshToken: null,
accessExpiresAt: null,
teamId: res.team_id
});
}
const exchangeCodeVercel = async ({ code }: { code: string }) => {
let res: ExchangeCodeVercelResponse;
try {
res = (
await axios.post(
INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({
code: code,
client_id: CLIENT_ID_VERCEL,
client_secret: CLIENT_SECRET_VERCEL,
redirect_uri: `${SITE_URL}/vercel`
} as any)
)
).data;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Vercel');
}
return {
accessToken: res.access_token,
refreshToken: null,
accessExpiresAt: null,
teamId: res.team_id
};
};
/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
@@ -184,58 +198,89 @@ const exchangeCodeVercel = async ({
* @returns {String} obj2.refreshToken - refresh token for Heroku API
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeNetlify = async ({
code
}: {
code: string;
}) => {
let res: ExchangeCodeNetlifyResponse;
let accountId;
try {
res = (await axios.post(
INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: CLIENT_ID_NETLIFY,
client_secret: CLIENT_SECRET_NETLIFY,
redirect_uri: `${SITE_URL}/netlify`
} as any)
)).data;
const exchangeCodeNetlify = async ({ code }: { code: string }) => {
let res: ExchangeCodeNetlifyResponse;
let accountId;
try {
res = (
await axios.post(
INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: CLIENT_ID_NETLIFY,
client_secret: CLIENT_SECRET_NETLIFY,
redirect_uri: `${SITE_URL}/netlify`
} as any)
)
).data;
const res2 = await axios.get(
'https://api.netlify.com/api/v1/sites',
{
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
const res2 = await axios.get('https://api.netlify.com/api/v1/sites', {
headers: {
Authorization: `Bearer ${res.access_token}`
}
});
}
export {
exchangeCode
}
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
};
};
/**
* 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 };

View File

@@ -13,44 +13,44 @@ import {
* named [integration]
* @param {Object} obj
* @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 ({
integration,
refreshToken
integration,
refreshToken
}: {
integration: string;
refreshToken: string;
integration: string;
refreshToken: string;
}) => {
let accessToken;
try {
switch (integration) {
case INTEGRATION_HEROKU:
accessToken = await exchangeRefreshHeroku({
refreshToken
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token');
let accessToken;
try {
switch (integration) {
case INTEGRATION_HEROKU:
accessToken = await exchangeRefreshHeroku({
refreshToken
});
break;
}
return accessToken;
}
} catch (err) {
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
* Heroku integration
* @param {Object} obj
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
* @returns
* @returns
*/
const exchangeRefreshHeroku = async ({
refreshToken
refreshToken
}: {
refreshToken: string;
refreshToken: string;
}) => {
let accessToken;
//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)
);
accessToken = res.data.access_token;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token for Heroku');
}
return accessToken;
}
accessToken = res.data.access_token;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token for Heroku');
}
export {
exchangeRefresh
}
return accessToken;
};
export { exchangeRefresh };

View File

@@ -1,50 +1,47 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import { IIntegrationAuth, IntegrationAuth, Integration } from '../models';
import {
IIntegrationAuth,
IntegrationAuth,
Integration
} from '../models';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
} from '../variables';
const revokeAccess = async ({
integrationAuth,
accessToken
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth,
accessToken: string
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
try {
// add any integration-specific revocation logic
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
break;
case INTEGRATION_VERCEL:
break;
case INTEGRATION_NETLIFY:
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');
try {
// add any integration-specific revocation logic
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
break;
case INTEGRATION_VERCEL:
break;
case INTEGRATION_NETLIFY:
break;
case INTEGRATION_GITHUB:
break;
}
}
export {
revokeAccess
}
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 { revokeAccess };

View File

@@ -1,16 +1,21 @@
import axios from 'axios';
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 {
IIntegration, IIntegrationAuth
} from '../models';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL
} from '../variables';
import { access, appendFile } from 'fs';
// TODO: need a helper function in the future to handle integration
// 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
*/
const syncSecrets = async ({
integration,
integrationAuth,
secrets,
accessToken,
integration,
integrationAuth,
secrets,
accessToken
}: {
integration: IIntegration;
integrationAuth: IIntegrationAuth;
secrets: any;
accessToken: string;
integration: IIntegration;
integrationAuth: IIntegrationAuth;
secrets: any;
accessToken: string;
}) => {
try {
switch (integration.integration) {
case INTEGRATION_HEROKU:
await syncSecretsHeroku({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_VERCEL:
await syncSecretsVercel({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_NETLIFY:
await syncSecretsNetlify({
integration,
integrationAuth,
secrets,
accessToken
});
break;
}
// TODO: set integration to inactive if it was not synced correctly (send alert?)
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to integration');
try {
switch (integration.integration) {
case INTEGRATION_HEROKU:
await syncSecretsHeroku({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_VERCEL:
await syncSecretsVercel({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_NETLIFY:
await syncSecretsNetlify({
integration,
integrationAuth,
secrets,
accessToken
});
break;
case INTEGRATION_GITHUB:
await syncSecretsGitHub({
integration,
secrets,
accessToken
});
break;
}
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to integration');
}
};
/**
* Sync/push [secrets] to Heroku [app]
@@ -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)
*/
const syncSecretsHeroku = async ({
integration,
secrets,
accessToken
integration,
secrets,
accessToken
}: {
integration: IIntegration,
secrets: any;
accessToken: string;
integration: IIntegration;
secrets: any;
accessToken: string;
}) => {
try {
const herokuSecrets = (await axios.get(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
{
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}`
}
}
)).data;
Object.keys(herokuSecrets).forEach(key => {
if (!(key in secrets)) {
secrets[key] = null;
}
});
try {
const herokuSecrets = (
await axios.get(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
{
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}`
}
}
)
).data;
await axios.patch(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
secrets,
{
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}`
}
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to Heroku');
}
}
Object.keys(herokuSecrets).forEach((key) => {
if (!(key in secrets)) {
secrets[key] = null;
}
});
await axios.patch(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
secrets,
{
headers: {
Accept: 'application/vnd.heroku+json; version=3',
Authorization: `Bearer ${accessToken}`
}
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to Heroku');
}
};
/**
* Sync/push [secrets] to Heroku [app]
@@ -474,8 +486,120 @@ const syncSecretsNetlify = async ({
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 };

View File

@@ -1,77 +1,83 @@
import { Schema, model, Types } from 'mongoose';
import {
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
} from '../variables';
export interface IIntegration {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: 'dev' | 'test' | 'staging' | 'prod';
isActive: boolean;
app: string;
target: string;
context: string;
siteId: string;
integration: 'heroku' | 'vercel' | 'netlify';
integrationAuth: Types.ObjectId;
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: 'dev' | 'test' | 'staging' | 'prod';
isActive: boolean;
app: string;
target: string;
context: string;
siteId: string;
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
integrationAuth: Types.ObjectId;
}
const integrationSchema = new Schema<IIntegration>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
isActive: {
type: Boolean,
required: true
},
app: { // name of app in provider
type: String,
default: null
},
target: { // vercel-specific target (environment)
type: String,
default: null
},
context: { // netlify-specific context (deploy)
type: String,
default: null
},
siteId: { // netlify-specific site (app) id
type: String,
default: null
},
integration: {
type: String,
enum: [
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
],
required: true
},
integrationAuth: {
type: Schema.Types.ObjectId,
ref: 'IntegrationAuth',
required: true
}
},
{
timestamps: true
}
{
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
isActive: {
type: Boolean,
required: true
},
app: {
// name of app in provider
type: String,
default: null
},
target: {
// vercel-specific target (environment)
type: String,
default: null
},
context: {
// netlify-specific context (deploy)
type: String,
default: null
},
siteId: {
// netlify-specific site (app) id
type: String,
default: null
},
integration: {
type: String,
enum: [
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
],
required: true
},
integrationAuth: {
type: Schema.Types.ObjectId,
ref: 'IntegrationAuth',
required: true
}
},
{
timestamps: true
}
);
const Integration = model<IIntegration>('Integration', integrationSchema);

View File

@@ -1,83 +1,87 @@
import { Schema, model, Types } from 'mongoose';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
} from '../variables';
export interface IIntegrationAuth {
_id: Types.ObjectId;
workspace: Types.ObjectId;
integration: 'heroku' | 'vercel' | 'netlify';
teamId: string;
accountId: string;
refreshCiphertext?: string;
refreshIV?: string;
refreshTag?: string;
accessCiphertext?: string;
accessIV?: string;
accessTag?: string;
accessExpiresAt?: Date;
_id: Types.ObjectId;
workspace: Types.ObjectId;
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
teamId: string;
accountId: string;
refreshCiphertext?: string;
refreshIV?: string;
refreshTag?: string;
accessCiphertext?: string;
accessIV?: string;
accessTag?: string;
accessExpiresAt?: Date;
}
const integrationAuthSchema = new Schema<IIntegrationAuth>(
{
workspace: {
type: Schema.Types.ObjectId,
required: true
},
integration: {
type: String,
enum: [
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
],
required: true
},
teamId: { // vercel-specific integration param
type: String
},
accountId: { // netlify-specific integration param
type: String
},
refreshCiphertext: {
type: String,
select: false
},
refreshIV: {
type: String,
select: false
},
refreshTag: {
type: String,
select: false
},
accessCiphertext: {
type: String,
select: false
},
accessIV: {
type: String,
select: false
},
accessTag: {
type: String,
select: false
},
accessExpiresAt: {
type: Date,
select: false
}
},
{
timestamps: true
}
{
workspace: {
type: Schema.Types.ObjectId,
required: true
},
integration: {
type: String,
enum: [
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
],
required: true
},
teamId: {
// vercel-specific integration param
type: String
},
accountId: {
// netlify-specific integration param
type: String
},
refreshCiphertext: {
type: String,
select: false
},
refreshIV: {
type: String,
select: false
},
refreshTag: {
type: String,
select: false
},
accessCiphertext: {
type: String,
select: false
},
accessIV: {
type: String,
select: false
},
accessTag: {
type: String,
select: false
},
accessExpiresAt: {
type: Date,
select: false
}
},
{
timestamps: true
}
);
const IntegrationAuth = model<IIntegrationAuth>(
'IntegrationAuth',
integrationAuthSchema
'IntegrationAuth',
integrationAuthSchema
);
export default IntegrationAuth;

View File

@@ -34,6 +34,4 @@ router.get(
keyController.getLatestKey
);
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
export default router;

View File

@@ -1,5 +1,6 @@
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 * as Sentry from '@sentry/node';
@@ -7,6 +8,7 @@ const mailOpts: SMTPConnection.Options = {
host: SMTP_HOST,
port: SMTP_PORT as number
};
if (SMTP_USERNAME && SMTP_PASSWORD) {
mailOpts.auth = {
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 = () => {
const transporter = nodemailer.createTransport(mailOpts);
transporter

View File

@@ -1,5 +0,0 @@
const ACTION_PUSH_TO_HEROKU = 'pushToHeroku';
export {
ACTION_PUSH_TO_HEROKU
}

View File

@@ -1,79 +1,75 @@
import {
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
ENV_SET
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
ENV_SET
} from './environment';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_OPTIONS
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL,
INTEGRATION_OPTIONS
} from './integration';
import {
OWNER,
ADMIN,
MEMBER,
INVITED,
ACCEPTED,
COMPLETED,
GRANTED
OWNER,
ADMIN,
MEMBER,
INVITED,
ACCEPTED,
COMPLETED,
GRANTED
} from './organization';
import {
SECRET_SHARED,
SECRET_PERSONAL
} from './secret';
import {
PLAN_STARTER,
PLAN_PRO
} from './stripe';
import {
EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS
} from './event';
import {
ACTION_PUSH_TO_HEROKU
} from './action';
import { SECRET_SHARED, SECRET_PERSONAL } from './secret';
import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event';
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from './smtp';
import { PLAN_STARTER, PLAN_PRO } from './stripe';
export {
OWNER,
ADMIN,
MEMBER,
INVITED,
ACCEPTED,
COMPLETED,
GRANTED,
PLAN_STARTER,
PLAN_PRO,
SECRET_SHARED,
SECRET_PERSONAL,
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
ENV_SET,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS,
ACTION_PUSH_TO_HEROKU,
INTEGRATION_OPTIONS
};
OWNER,
ADMIN,
MEMBER,
INVITED,
ACCEPTED,
COMPLETED,
GRANTED,
SECRET_SHARED,
SECRET_PERSONAL,
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD,
ENV_SET,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL,
EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS,
INTEGRATION_OPTIONS,
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
PLAN_STARTER,
PLAN_PRO,
};

View File

@@ -1,6 +1,7 @@
import {
CLIENT_ID_HEROKU,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SLUG_VERCEL
} from '../config';
@@ -8,10 +9,12 @@ import {
const INTEGRATION_HEROKU = 'heroku';
const INTEGRATION_VERCEL = 'vercel';
const INTEGRATION_NETLIFY = 'netlify';
const INTEGRATION_GITHUB = 'github';
const INTEGRATION_SET = new Set([
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
]);
// integration types
@@ -19,13 +22,17 @@ const INTEGRATION_OAUTH2 = 'oauth2';
// integration oauth endpoints
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_GITHUB_TOKEN_URL =
'https://github.com/login/oauth/access_token';
// integration apps endpoints
const INTEGRATION_HEROKU_API_URL = 'https://api.heroku.com';
const INTEGRATION_VERCEL_API_URL = 'https://api.vercel.com';
const INTEGRATION_NETLIFY_API_URL = 'https://api.netlify.com';
const INTEGRATION_GITHUB_API_URL = 'https://api.github.com';
const INTEGRATION_OPTIONS = [
{
@@ -56,6 +63,16 @@ const INTEGRATION_OPTIONS = [
clientId: CLIENT_ID_NETLIFY,
docsLink: ''
},
{
name: 'GitHub',
slug: 'github',
image: 'GitHub',
isAvailable: true,
type: 'oauth2',
clientId: CLIENT_ID_GITHUB,
docsLink: ''
},
{
name: 'Google Cloud Platform',
slug: 'gcp',
@@ -104,16 +121,19 @@ const INTEGRATION_OPTIONS = [
]
export {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_OPTIONS
}
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_GITHUB_API_URL,
INTEGRATION_OPTIONS
};

View 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
}

View File

@@ -3,24 +3,37 @@ module github.com/Infisical/infisical-merge
go 1.19
require (
github.com/99designs/keyring v1.2.2
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.3.0
golang.org/x/term v0.3.0
)
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/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/sys v0.2.0 // indirect
golang.org/x/sys v0.3.0 // indirect
)
require (
github.com/go-resty/resty/v2 v2.7.0
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/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/zalando/go-keyring v0.2.1
)

View File

@@ -1,5 +1,9 @@
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
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.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
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/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
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/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/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/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/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
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/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
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/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
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-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-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-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.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
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.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.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/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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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-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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
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.`,
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.
@@ -31,4 +31,8 @@ func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
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.PersistentPreRun = func(cmd *cobra.Command, args []string) {
util.InitKeyRingInstance()
}
}

98
cli/packages/cmd/vault.go Normal file
View 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)
}

View File

@@ -1,5 +1,7 @@
package models
import "github.com/99designs/keyring"
type UserCredentials struct {
Email string `json:"email"`
PrivateKey string `json:"privateKey"`
@@ -8,7 +10,8 @@ type UserCredentials struct {
// The file struct for Infisical config file
type ConfigFile struct {
LoggedInUserEmail string `json:"loggedInUserEmail"`
LoggedInUserEmail string `json:"loggedInUserEmail"`
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
}
type SingleEnvironmentVariable struct {

View File

@@ -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{
LoggedInUserEmail: userCredentials.Email,
VaultBackendType: existingConfigFile.VaultBackendType,
}
configFileMarshalled, err := json.Marshal(configFile)
@@ -152,3 +159,50 @@ func GetAllWorkSpaceConfigsStartingFromCurrentPath() (workspaces []models.Worksp
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
}

View File

@@ -3,12 +3,11 @@ package util
import (
"encoding/json"
"fmt"
"os"
"github.com/99designs/keyring"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"github.com/zalando/go-keyring"
)
const SERVICE_NAME = "infisical"
@@ -17,26 +16,30 @@ const SERVICE_NAME = "infisical"
func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
userCredMarshalled, err := json.Marshal(userCred)
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 {
return fmt.Errorf("Unable to store user credentials:", err)
return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err)
}
return err
}
func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) {
credentialsString, err := keyring.Get(SERVICE_NAME, userEmail)
credentialsValue, err := keyringInstance.Get(userEmail)
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
err = json.Unmarshal([]byte(credentialsString), &userCredentials)
err = json.Unmarshal([]byte(credentialsValue.Data), &userCredentials)
if err != nil {
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) {
if ConfigFileExists() {
fullConfigFilePath, _, err := GetFullConfigFilePath()
configFile, err := GetConfigFile()
if err != nil {
log.Debugln("Error gettting full path:", err)
return false, "", err
return false, "", fmt.Errorf("IsUserLoggedIn: unable to get logged in user from config file [err=%s]", err)
}
configFileAsBytes, err := os.ReadFile(fullConfigFilePath)
if err != 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
if configFile.LoggedInUserEmail == "" {
return false, "", nil
}
userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail)

View 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
}

View 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()
}

View File

@@ -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`.

View File

@@ -16,59 +16,44 @@ cd infisical
## 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
`.env.example` as an example guide.
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.
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.
![image](https://user-images.githubusercontent.com/8071263/208800914-f468c2ad-c6a8-4da7-8ffd-eece5d7f08d2.png)
### 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:
```
PRIVATE_KEY='oGVv5rThrpZ7WLgQW27chY1cXngr4wLQIZnGfSKgHPk='
PUBLIC_KEY='ldr6JaC7AY+tun3omGLdE4SWpkJbtVBOI54KfUP53Xc='
```
- `ENCRYPTION_KEY` should be a [32-byte random hex](https://www.browserling.com/tools/random-hex)
- `MONGO_URL` should take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`.
</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).
![image](https://user-images.githubusercontent.com/118568289/206792171-3376e3c6-c3ac-4d5d-8776-d78ee089b520.png)
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
![image](https://user-images.githubusercontent.com/118568289/206792653-ba3211d1-1071-43f2-93a7-8b408bbd9e0e.png)
If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following.
For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification.
For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable.
Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this:
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>
```
SMTP_HOST=smtp-server
SMTP_PORT=1025
SMTP_NAME=<whatever you like>
SMTP_FROM_ADDRESS=team@infisical.com
SMTP_FROM_NAME=Infisical
SMTP_USERNAME=team@infisical.com
SMTP_PASSWORD=
```
Make sure to leave the `SMTP_PASSWORD` blank so the backend will be able to connect to MailHog
You can browse `http://localhost:8025/` to browse email messages sent by the backend.
With these environment variables, you will be ready to run the docker-compose.
<Warning>
If using Mailhog, make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog.
</Warning>
## Docker for development
@@ -84,12 +69,4 @@ Then browse http://localhost:8080
docker-compose -f docker-compose.dev.yml down
# start services
docker-compose -f docker-compose.dev.yml up
```
The docker-compose development environment consists of:
- nginx
- frontend
- backend
- mongo
- mongo-express
```

View File

@@ -8,7 +8,7 @@ Note that the Infisical CLI is platform-agnostic and can inject environment vari
## 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.
3. Populate your environment variables as in the image below.

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,34 @@
---
title: "GitHub Actions"
---
<Warning>
Infisical can sync secrets to GitHub repo secrets only. If your repo uses environment secrets, then stay tuned with this [issue](https://github.com/Infisical/infisical/issues/54).
</Warning>
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Ensure you have admin privileges to the repo you want to sync secrets to.
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for GitHub
Press on the GitHub tile and grant Infisical access to your GitHub account (repo privileges only).
![integrations github authorization](../../images/integrations-github-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which GitHub repo and press start integration to start syncing secrets to the repo.
![integrations github](../../images/integrations-github.png)

View File

@@ -13,7 +13,8 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
| [Heroku](/integrations/cloud/heroku) | 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 |
| [Vue](/integrations/frameworks/vue) | Framework | Available |
| [Express](/integrations/frameworks/express) | Framework | Available |

View File

@@ -102,18 +102,21 @@
{
"group": "Self-hosting",
"pages": [
"self-hosting/overview",
{
"group": "Deployments options",
"pages": [
"self-hosting/deployments/linux",
"self-hosting/deployments/kubernetes"
]
},
{
"group": "Configuration",
"pages": ["self-hosting/configuration/envars"]
}
"self-hosting/overview"
]
},
{
"group": "Deployment options",
"pages": [
"self-hosting/deployments/linux",
"self-hosting/deployments/kubernetes"
]
},
{
"group": "Configuration",
"pages": [
"self-hosting/configuration/envars",
"self-hosting/configuration/email"
]
},
{
@@ -140,7 +143,10 @@
},
{
"group": "CI/CD",
"pages": ["integrations/cicd/circleci"]
"pages": [
"integrations/cicd/githubactions",
"integrations/cicd/circleci"
]
},
{
"group": "Frameworks",
@@ -174,8 +180,7 @@
"pages": [
"contributing/overview",
"contributing/code-of-conduct",
"contributing/developing",
"contributing/FAQ"
"contributing/developing"
]
}
],

View 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:
![creating sendgrid api key](../../images/email-sendgrid-create-key.png)
![setting sendgrid api key restriction](../../images/email-sendgrid-restrictions.png)
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
![obtain mailhog api key estriction](../../images/email-mailhog-credentials.png)
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>

View File

@@ -3,18 +3,15 @@ title: "Environment Variables"
description: ""
---
## The .env file
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.
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.
| 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` |
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_REFRESH_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_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` |
@@ -23,14 +20,21 @@ Configuring Infisical requires setting some environment variables. There is a fi
| `MONGO_USERNAME` | MongoDB username 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` |
| `SMTP_HOST` | Hostname to connect to for establishing SMTP connections | `smtp.gmail.com` |
| `SMTP_NAME` | Name label to be used in From field (e.g. `Team`) | `None` |
| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` |
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `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` |
| `CLIENT_ID_VERCEL` | OAuth client id for Vercel integration | `None` |
| `CLIENT_ID_NETLIFY` | OAuth client id for Netlify integration | `None` |
| `CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` |
| `CLIENT_SECRET_VERCEL` | OAuth client secret for Vercel integration | `None` |
| `CLIENT_SECRET_NETLIFY` | OAuth client secret for Netlify integration | `None` |
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub 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` |

View File

@@ -42,7 +42,7 @@ that by adding the `--namespace <namespace-to-install-to>` to your `helm install
```bash
## 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>
@@ -50,5 +50,4 @@ If you have not filled out all of the required environment variables, you will s
do so.
</Note>
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.
#### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.

View File

@@ -209,7 +209,7 @@ const AddServiceTokenDialog = ({
</div>
</div>
<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
type="text"
value={serviceToken}

View File

@@ -203,7 +203,7 @@ const Integration = ({
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
siteId
});
router.reload();
}}
color="mineshaft"

28
frontend/pages/404.tsx Normal file
View 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
View File

@@ -0,0 +1,37 @@
import React, { useEffect } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
const queryString = require("query-string");
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
export default function Github() {
const router = useRouter();
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
const code = parsedUrl.code;
const state = parsedUrl.state;
/**
* Here we forward to the default workspace if a user opens this url
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(async () => {
try {
if (state === localStorage.getItem('latestCSRFToken')) {
localStorage.removeItem('latestCSRFToken');
await AuthorizeIntegration({
workspaceId: localStorage.getItem('projectData.id'),
code,
integration: "github",
});
router.push("/integrations/" + localStorage.getItem("projectData.id"));
}
} catch (error) {
console.error('Github integration error: ', error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div></div>;
}
Github.requireAuth = true;

View File

@@ -123,6 +123,8 @@ export default function Integrations() {
* @returns
*/
const handleIntegrationOption = async ({ integrationOption }) => {
console.log('handleIntegrationOption', integrationOption);
try {
// generate CSRF token for OAuth2 code-token exchange integrations
@@ -139,6 +141,9 @@ export default function Integrations() {
case 'Netlify':
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
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':
// console.log('fly.io');
// setIntegrationAccessTokenDialogOpen(true);

View File

@@ -1,21 +1,30 @@
import React from "react";
import Image from "next/image";
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function NoProjects() {
return (
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
<FontAwesomeIcon
icon={faFolderOpen}
className="text-7xl mb-8 w-full px-auto"
/>
<div className="max-w-md">
You are not part of any projects in this organization yet. When you do,
they will appear here.
<div
className="mb-4 mr-16"
>
<Image
src="/images/dragon-cant-find.png"
height={270}
width={436}
alt="google logo"
></Image>
</div>
<div className="max-w-md mt-4">
Create a new project, or ask other organization members to give you
neccessary permissions.
<div className="p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl">
<div className="max-w-md">
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>
);

View File

@@ -40,8 +40,8 @@ const props = {
backgroundColor: '#0d1117',
color: 'white',
border: '1px solid gray',
textAlign: 'center'
}
textAlign: 'center',
},
} as const;
const propsPhone = {
inputStyle: {
@@ -56,8 +56,8 @@ const propsPhone = {
backgroundColor: '#0d1117',
color: 'white',
border: '1px solid gray',
textAlign: 'center'
}
textAlign: 'center',
},
} as const;
export default function SignUp() {
@@ -81,6 +81,7 @@ export default function SignUp() {
const router = useRouter();
const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState('');
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 () => {
setIsLoading(true);
let errorCheck = false;
@@ -169,7 +171,7 @@ export default function SignUp() {
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: errorCheck
currentErrorCheck: errorCheck,
});
if (!errorCheck) {
@@ -186,8 +188,8 @@ export default function SignUp() {
.slice(0, 32)
.padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size),
'0'
)
'0',
),
}) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
@@ -195,7 +197,7 @@ export default function SignUp() {
client.init(
{
username: email,
password: password
password: password,
},
async () => {
client.createVerifier(
@@ -204,14 +206,14 @@ export default function SignUp() {
email,
firstName,
lastName,
organizationName: firstName + "'s organization",
organizationName: firstName + '\'s organization',
publicKey: PUBLIC_KEY,
ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken
token: verificationToken,
});
// if everything works, go the main dashboard page.
@@ -230,27 +232,37 @@ export default function SignUp() {
setErrorLogin,
router,
true,
false
false,
);
incrementStep();
} catch (error) {
setIsLoading(false);
}
}
}
},
);
}
},
);
} else {
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)
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">
<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>
<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">
@@ -284,7 +296,7 @@ export default function SignUp() {
acknowledged the Privacy Policy.
</p>
<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>
@@ -294,7 +306,7 @@ export default function SignUp() {
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">
<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 className="text-l flex justify-center font-semibold my-2 text-gray-400">
{email}{' '}
@@ -328,13 +340,16 @@ export default function SignUp() {
<Button text="Verify" onButtonPressed={incrementStep} size="lg" />
</div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
{/* <Link href="/login">
<button className="w-full hover:opacity-90 duration-200">
<u className="font-normal text-sm text-sky-700">
Not seeing an email? Resend
</u>
</button>
</Link> */}
<div className="flex flex-row items-baseline gap-1">
<span className="text-gray-400">
Not seeing an email?
</span>
<u className={`font-normal text-sm ${isResendingVerificationEmail ? 'text-gray-400' : 'text-sky-500 hover:opacity-90 duration-200'}`}>
<button disabled={isLoading} onClick={resendVerificationEmail}>
{isResendingVerificationEmail ? 'Resending...' : 'Resend'}
</button>
</u>
</div>
<p className="text-sm text-gray-500 pb-2">
Make sure to check your spam inbox.
</p>
@@ -382,7 +397,7 @@ export default function SignUp() {
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck: false
currentErrorCheck: false,
});
}}
type="password"
@@ -506,7 +521,7 @@ export default function SignUp() {
password,
personalName: firstName + ' ' + lastName,
setBackupKeyError,
setBackupKeyIssued
setBackupKeyIssued,
});
const userWorkspaces = await getWorkspaces();
const userWorkspace = userWorkspaces[0]._id;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -7,7 +7,7 @@ type: application
# 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.
# 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
# incremented each time you make changes to the application. Versions are not expected to

View File

@@ -34,7 +34,7 @@ spec:
{{- end }}
{{- end }}
ports:
- containerPort: 4000
- containerPort: 3000
---
apiVersion: v1
kind: Service

View File

@@ -22,7 +22,8 @@ backend:
ingress:
enabled: true
annotations: {}
annotations:
kubernetes.io/ingress.class: "nginx"
hostName: example.com
frontend:
path: /