Fixed merge conflicts

This commit is contained in:
Vladyslav Matsiiako
2022-12-26 17:13:16 -05:00
109 changed files with 2793 additions and 649 deletions
+12 -17
View File
@@ -1,15 +1,13 @@
# Keys # Keys
# Required keys for platform encryption/decryption ops # Required key for platform encryption/decryption ops
PRIVATE_KEY=replace_with_nacl_sk ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
PUBLIC_KEY=replace_with_nacl_pk
ENCRYPTION_KEY=replace_with_lengthy_secure_hex
# JWT # JWT
# Required secrets to sign JWT tokens # Required secrets to sign JWT tokens
JWT_SIGNUP_SECRET=replace_with_lengthy_secure_hex JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
JWT_REFRESH_SECRET=replace_with_lengthy_secure_hex JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
JWT_AUTH_SECRET=replace_with_lengthy_secure_hex JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
JWT_SERVICE_SECRET=replace_with_lengthy_secure_hex JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
# JWT lifetime # JWT lifetime
# Optional lifetimes for JWT tokens expressed in seconds or a string # Optional lifetimes for JWT tokens expressed in seconds or a string
@@ -33,19 +31,15 @@ MONGO_PASSWORD=example
# Website URL # Website URL
# Required # Required
SITE_URL=http://localhost:8080 SITE_URL=http://localhost:8080
# Mail/SMTP # Mail/SMTP
# Required to send emails SMTP_HOST= # required
# By default, SMTP_HOST is set to smtp.gmail.com, SMTP_PORT is set to 587, SMTP_TLS is set to false, and SMTP_FROM_NAME is set to Infisical SMTP_USERNAME= # required
SMTP_HOST=smtp.gmail.com SMTP_PASSWORD= # required
# If STARTTLS is supported, the connection will be upgraded to TLS when SMTP_SECURE is set to false
SMTP_SECURE=false
SMTP_PORT=587 SMTP_PORT=587
SMTP_USERNAME= SMTP_SECURE=false
SMTP_PASSWORD= SMTP_FROM_ADDRESS= # required
SMTP_FROM_ADDRESS=
SMTP_FROM_NAME=Infisical SMTP_FROM_NAME=Infisical
# Integration # Integration
@@ -58,6 +52,7 @@ CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL= CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY= CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB= CLIENT_SECRET_GITHUB=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors # Sentry (optional) for monitoring errors
SENTRY_DSN= SENTRY_DSN=
@@ -1,22 +0,0 @@
# name: Close inactive issues
# on:
# schedule:
# - cron: "30 1 * * *"
# jobs:
# close-issues:
# runs-on: ubuntu-latest
# permissions:
# issues: write
# pull-requests: write
# steps:
# - uses: actions/stale@v4
# with:
# days-before-issue-stale: 30
# days-before-issue-close: 14
# stale-issue-label: "stale"
# stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
# close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
# days-before-pr-stale: -1
# days-before-pr-close: -1
# repo-token: ${{ secrets.GITHUB_TOKEN }}
+10 -1
View File
@@ -13,7 +13,7 @@ permissions:
jobs: jobs:
goreleaser: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@@ -24,6 +24,15 @@ jobs:
go-version: '>=1.19.3' go-version: '>=1.19.3'
cache: true cache: true
cache-dependency-path: cli/go.sum cache-dependency-path: cli/go.sum
- name: libssl1.1 => libssl1.0-dev for OSXCross
run: |
echo 'deb http://security.ubuntu.com/ubuntu bionic-security main' | sudo tee -a /etc/apt/sources.list
sudo apt update && apt-cache policy libssl1.0-dev
sudo apt-get install libssl1.0-dev
- name: OSXCross for CGO Support
run: |
mkdir ../../osxcross
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
- uses: goreleaser/goreleaser-action@v2 - uses: goreleaser/goreleaser-action@v2
with: with:
distribution: goreleaser distribution: goreleaser
+15 -6
View File
@@ -7,12 +7,23 @@
# # you may remove this if you don't need go generate # # you may remove this if you don't need go generate
# - cd cli && go generate ./... # - cd cli && go generate ./...
builds: builds:
- env: - id: darwin-build
- CGO_ENABLED=0
binary: infisical binary: infisical
id: infisical env:
- CGO_ENABLED=1
- CC=/home/runner/work/osxcross/target/bin/o64-clang
- CXX=/home/runner/work/osxcross/target/bin/o64-clang++
goos: goos:
- darwin - darwin
ignore:
- goos: darwin
goarch: "386"
dir: ./cli
- id: all-other-builds
env:
- CGO_ENABLED=0
binary: infisical
goos:
- freebsd - freebsd
- linux - linux
- netbsd - netbsd
@@ -27,8 +38,6 @@ builds:
- 6 - 6
- 7 - 7
ignore: ignore:
- goos: darwin
goarch: "386"
- goos: windows - goos: windows
goarch: "386" goarch: "386"
- goos: freebsd - goos: freebsd
@@ -71,7 +80,7 @@ nfpms:
- id: infisical - id: infisical
package_name: infisical package_name: infisical
builds: builds:
- infisical - all-other-builds
vendor: Infisical, Inc vendor: Infisical, Inc
homepage: https://infisical.com/ homepage: https://infisical.com/
maintainer: Infisical, Inc maintainer: Infisical, Inc
+7 -3
View File
@@ -146,7 +146,9 @@ We're currently setting the foundation and building [integrations](https://infis
🔜 AWS 🔜 AWS
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
🔜 GitHub Actions (https://github.com/Infisical/infisical/issues/54) <a href="https://infisical.com/docs/integrations/cicd/githubactions">
✔️ GitHub Actions
</a>
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
🔜 Railway 🔜 Railway
@@ -179,7 +181,9 @@ We're currently setting the foundation and building [integrations](https://infis
🔜 TravisCI 🔜 TravisCI
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
🔜 Netlify (https://github.com/Infisical/infisical/issues/55) <a href="https://infisical.com/docs/integrations/cloud/netlify">
✔️ Netlify
</a>
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
🔜 Railway 🔜 Railway
@@ -317,4 +321,4 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
+2 -2
View File
@@ -14,6 +14,8 @@ declare global {
JWT_SIGNUP_SECRET: string; JWT_SIGNUP_SECRET: string;
MONGO_URL: string; MONGO_URL: string;
NODE_ENV: 'development' | 'staging' | 'testing' | 'production'; NODE_ENV: 'development' | 'staging' | 'testing' | 'production';
VERBOSE_ERROR_OUTPUT: string;
LOKI_HOST: string;
CLIENT_ID_HEROKU: string; CLIENT_ID_HEROKU: string;
CLIENT_ID_VERCEL: string; CLIENT_ID_VERCEL: string;
CLIENT_ID_NETLIFY: string; CLIENT_ID_NETLIFY: string;
@@ -22,8 +24,6 @@ declare global {
CLIENT_SECRET_NETLIFY: string; CLIENT_SECRET_NETLIFY: string;
POSTHOG_HOST: string; POSTHOG_HOST: string;
POSTHOG_PROJECT_API_KEY: string; POSTHOG_PROJECT_API_KEY: string;
PRIVATE_KEY: string;
PUBLIC_KEY: string;
SENTRY_DSN: string; SENTRY_DSN: string;
SITE_URL: string; SITE_URL: string;
SMTP_HOST: string; SMTP_HOST: string;
+669 -15
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -22,13 +22,15 @@
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"mongoose": "^6.7.2", "mongoose": "^6.7.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.2.0", "posthog-node": "^2.2.2",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3" "typescript": "^4.9.3",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
}, },
"name": "infisical-api", "name": "infisical-api",
"version": "1.0.0", "version": "1.0.0",
+19 -2
View File
@@ -1,5 +1,5 @@
/* eslint-disable no-console */
import { patchRouterParam } from './utils/patchAsyncRoutes';
import express from 'express'; import express from 'express';
import helmet from 'helmet'; import helmet from 'helmet';
import cors from 'cors'; import cors from 'cors';
@@ -29,6 +29,12 @@ import {
integration as integrationRouter, integration as integrationRouter,
integrationAuth as integrationAuthRouter integrationAuth as integrationAuthRouter
} from './routes'; } from './routes';
import { getLogger } from './utils/logger';
import { RouteNotFoundError } from './utils/errors';
import { requestErrorHandler } from './middleware/requestErrorHandler';
//* Patch Async route params to handle Promise Rejections
patchRouterParam()
export const app = express(); export const app = express();
@@ -69,6 +75,17 @@ app.use('/api/v1/stripe', stripeRouter);
app.use('/api/v1/integration', integrationRouter); app.use('/api/v1/integration', integrationRouter);
app.use('/api/v1/integration-auth', integrationAuthRouter); app.use('/api/v1/integration-auth', integrationAuthRouter);
//* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next)=>{
if(res.headersSent) return next();
next(RouteNotFoundError({message: `The requested source '(${req.method})${req.url}' was not found`}))
})
//* Error Handling Middleware (must be after all routing logic)
app.use(requestErrorHandler)
export const server = app.listen(PORT, () => { export const server = app.listen(PORT, () => {
console.log(`Listening on PORT ${[PORT]}`); getLogger("backend-main").info(`Server started listening at port ${PORT}`)
}); });
+7 -7
View File
@@ -10,6 +10,8 @@ const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m';
const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!;
const MONGO_URL = process.env.MONGO_URL!; const MONGO_URL = process.env.MONGO_URL!;
const NODE_ENV = process.env.NODE_ENV! || 'production'; const NODE_ENV = process.env.NODE_ENV! || 'production';
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
const LOKI_HOST = process.env.LOKI_HOST || undefined;
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!; const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!; const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
@@ -23,13 +25,11 @@ const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_PROJECT_API_KEY = const POSTHOG_PROJECT_API_KEY =
process.env.POSTHOG_PROJECT_API_KEY! || process.env.POSTHOG_PROJECT_API_KEY! ||
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
const PUBLIC_KEY = process.env.PUBLIC_KEY!;
const SENTRY_DSN = process.env.SENTRY_DSN!; const SENTRY_DSN = process.env.SENTRY_DSN!;
const SITE_URL = process.env.SITE_URL!; const SITE_URL = process.env.SITE_URL!;
const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com'; const SMTP_HOST = process.env.SMTP_HOST!;
const SMTP_SECURE = process.env.SMTP_SECURE! || false; const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false;
const SMTP_PORT = process.env.SMTP_PORT! || 587; const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587;
const SMTP_USERNAME = process.env.SMTP_USERNAME!; const SMTP_USERNAME = process.env.SMTP_USERNAME!;
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!; const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!;
@@ -55,6 +55,8 @@ export {
JWT_SIGNUP_SECRET, JWT_SIGNUP_SECRET,
MONGO_URL, MONGO_URL,
NODE_ENV, NODE_ENV,
VERBOSE_ERROR_OUTPUT,
LOKI_HOST,
CLIENT_ID_HEROKU, CLIENT_ID_HEROKU,
CLIENT_ID_VERCEL, CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY, CLIENT_ID_NETLIFY,
@@ -66,8 +68,6 @@ export {
CLIENT_SLUG_VERCEL, CLIENT_SLUG_VERCEL,
POSTHOG_HOST, POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY, POSTHOG_PROJECT_API_KEY,
PRIVATE_KEY,
PUBLIC_KEY,
SENTRY_DSN, SENTRY_DSN,
SITE_URL, SITE_URL,
SMTP_HOST, SMTP_HOST,
+1 -14
View File
@@ -2,7 +2,6 @@ import { Request, Response } from 'express';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { Key } from '../models'; import { Key } from '../models';
import { findMembership } from '../helpers/membership'; import { findMembership } from '../helpers/membership';
import { PUBLIC_KEY } from '../config';
import { GRANTED } from '../variables'; import { GRANTED } from '../variables';
/** /**
@@ -84,16 +83,4 @@ export const getLatestKey = async (req: Request, res: Response) => {
} }
return res.status(200).send(resObj); return res.status(200).send(resObj);
}; };
/**
* Return public key of Infisical
* @param req
* @param res
* @returns
*/
export const getPublicKeyInfisical = async (req: Request, res: Response) => {
return res.status(200).send({
publicKey: PUBLIC_KEY
});
};
@@ -58,7 +58,8 @@ export const createServiceToken = async (req: Request, res: Response) => {
token = createToken({ token = createToken({
payload: { payload: {
serviceTokenId: serviceToken._id.toString() serviceTokenId: serviceToken._id.toString(),
workspaceId
}, },
expiresIn: expiresIn, expiresIn: expiresIn,
secret: JWT_SERVICE_SECRET secret: JWT_SERVICE_SECRET
@@ -10,6 +10,7 @@ import {
} from '../helpers/signup'; } from '../helpers/signup';
import { issueTokens, createToken } from '../helpers/auth'; import { issueTokens, createToken } from '../helpers/auth';
import { INVITED, ACCEPTED } from '../variables'; import { INVITED, ACCEPTED } from '../variables';
import axios from 'axios';
/** /**
* Signup step 1: Initialize account for user under email [email] and send a verification code * Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -179,6 +180,21 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
token = tokens.token; token = tokens.token;
refreshToken = tokens.refreshToken; refreshToken = tokens.refreshToken;
// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
await axios.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + process.env.LOOPS_API_KEY
},
});
}
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
+15 -7
View File
@@ -2,18 +2,18 @@ import * as Sentry from '@sentry/node';
import { import {
Bot, Bot,
Integration, Integration,
IIntegration,
IntegrationAuth, IntegrationAuth,
IIntegrationAuth
} from '../models'; } from '../models';
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations'; import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
import { BotService, IntegrationService } from '../services'; import { BotService } from '../services';
import { import {
ENV_DEV, ENV_DEV,
EVENT_PUSH_SECRETS, EVENT_PUSH_SECRETS,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY INTEGRATION_NETLIFY
} from '../variables'; } from '../variables';
import { UnauthorizedRequestError } from '../utils/errors';
import RequestError from '../utils/requestError';
interface Update { interface Update {
workspace: string; workspace: string;
@@ -176,12 +176,13 @@ const syncIntegrationsHelper = async ({
*/ */
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => { const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
let refreshToken; let refreshToken;
try { try {
const integrationAuth = await IntegrationAuth const integrationAuth = await IntegrationAuth
.findById(integrationAuthId) .findById(integrationAuthId)
.select('+refreshCiphertext +refreshIV +refreshTag'); .select('+refreshCiphertext +refreshIV +refreshTag');
if (!integrationAuth) throw new Error('Failed to find integration auth'); if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
refreshToken = await BotService.decryptSymmetric({ refreshToken = await BotService.decryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace.toString(),
@@ -193,7 +194,10 @@ const syncIntegrationsHelper = async ({
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to get integration refresh token'); if(err instanceof RequestError)
throw err
else
throw new Error('Failed to get integration refresh token');
} }
return refreshToken; return refreshToken;
@@ -209,12 +213,13 @@ const syncIntegrationsHelper = async ({
*/ */
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => { const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
let accessToken; let accessToken;
try { try {
const integrationAuth = await IntegrationAuth const integrationAuth = await IntegrationAuth
.findById(integrationAuthId) .findById(integrationAuthId)
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext'); .select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext');
if (!integrationAuth) throw new Error('Failed to find integration auth'); if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
accessToken = await BotService.decryptSymmetric({ accessToken = await BotService.decryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace.toString(),
@@ -240,7 +245,10 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
throw new Error('Failed to get integration access token'); if(err instanceof RequestError)
throw err
else
throw new Error('Failed to get integration access token');
} }
return accessToken; return accessToken;
+1
View File
@@ -21,6 +21,7 @@ const validateMembership = async ({
}) => { }) => {
let membership; let membership;
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
try { try {
membership = await Membership.findOne({ membership = await Membership.findOne({
user: userId, user: userId,
+1 -2
View File
@@ -9,8 +9,7 @@ import {
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITHUB_API_URL, INTEGRATION_GITHUB_API_URL
ACTION_PUSH_TO_HEROKU
} from '../variables'; } from '../variables';
import { import {
SITE_URL, SITE_URL,
+1
View File
@@ -53,6 +53,7 @@ const exchangeRefreshHeroku = async ({
refreshToken: string; refreshToken: string;
}) => { }) => {
let accessToken; let accessToken;
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
try { try {
const res = await axios.post( const res = await axios.post(
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
@@ -0,0 +1,29 @@
import { ErrorRequestHandler } from "express";
import * as Sentry from '@sentry/node';
import { InternalServerError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError";
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError|Error, req, res, next) => {
if(res.headersSent) return next();
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if(!(error instanceof RequestError)){
error = InternalServerError({context: {exception: error.message}, stack: error.stack})
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
}
//* Set Sentry user identification if req.user is populated
if(req.user !== undefined && req.user !== null){
Sentry.setUser({ email: req.user.email })
}
//* Only sent error to Sentry if LogLevel is one of the following level 'ERROR', 'EMERGENCY' or 'CRITICAL'
//* with this we will eliminate false-positive errors like 'BadRequestError', 'UnauthorizedRequestError' and so on
if([LogLevel.ERROR, LogLevel.EMERGENCY, LogLevel.CRITICAL].includes((<RequestError>error).level)){
Sentry.captureException(error)
}
res.status((<RequestError>error).statusCode).json((<RequestError>error).format(req))
next()
}
+16 -23
View File
@@ -1,8 +1,8 @@
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import * as Sentry from '@sentry/node';
import { User } from '../models'; import { User } from '../models';
import { JWT_AUTH_SECRET } from '../config'; import { JWT_AUTH_SECRET } from '../config';
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
declare module 'jsonwebtoken' { declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -20,32 +20,25 @@ declare module 'jsonwebtoken' {
*/ */
const requireAuth = async (req: Request, res: Response, next: NextFunction) => { const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
// JWT authentication middleware // JWT authentication middleware
try { const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
if (!req.headers?.authorization) if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
throw new Error('Failed to locate authorization header'); if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const token = req.headers.authorization.split(' ')[1]; const decodedToken = <jwt.UserIDJwtPayload>(
const decodedToken = <jwt.UserIDJwtPayload>( jwt.verify(AUTH_TOKEN_VALUE, JWT_AUTH_SECRET)
jwt.verify(token, JWT_AUTH_SECRET) );
);
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId _id: decodedToken.userId
}).select('+publicKey'); }).select('+publicKey');
if (!user) throw new Error('Failed to authenticate unfound user'); if (!user) return next(AccountNotFoundError({message: 'Failed to locate User account'}))
if (!user?.publicKey) if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account'); return next(UnauthorizedRequestError({message: 'Unable to authenticate due to partially set up account'}))
req.user = user; req.user = user;
return next(); return next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed to authenticate user. Try logging in'
});
}
}; };
export default requireAuth; export default requireAuth;
+16 -24
View File
@@ -1,7 +1,7 @@
import * as Sentry from '@sentry/node';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { Bot } from '../models'; import { Bot } from '../models';
import { validateMembership } from '../helpers/membership'; import { validateMembership } from '../helpers/membership';
import { AccountNotFoundError } from '../utils/errors';
type req = 'params' | 'body' | 'query'; type req = 'params' | 'body' | 'query';
@@ -15,30 +15,22 @@ const requireBotAuth = ({
location?: req; location?: req;
}) => { }) => {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
try { const bot = await Bot.findOne({ _id: req[location].botId });
const bot = await Bot.findOne({ _id: req[location].botId });
if (!bot) {
if (!bot) { return next(AccountNotFoundError({message: 'Failed to locate Bot account'}))
throw new Error('Failed to find bot');
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: bot.workspace.toString(),
acceptedRoles,
acceptedStatuses
});
req.bot = bot;
next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed bot authorization'
});
} }
await validateMembership({
userId: req.user._id.toString(),
workspaceId: bot.workspace.toString(),
acceptedRoles,
acceptedStatuses
});
req.bot = bot;
next();
} }
} }
@@ -1,8 +1,8 @@
import * as Sentry from '@sentry/node';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { Bot, Integration, IntegrationAuth, Membership } from '../models'; import { Integration, IntegrationAuth } from '../models';
import { IntegrationService } from '../services'; import { IntegrationService } from '../services';
import { validateMembership } from '../helpers/membership'; import { validateMembership } from '../helpers/membership';
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
/** /**
* Validate if user on request is a member of workspace with proper roles associated * Validate if user on request is a member of workspace with proper roles associated
@@ -21,48 +21,40 @@ const requireIntegrationAuth = ({
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
// integration authorization middleware // integration authorization middleware
try { const { integrationId } = req.params;
const { integrationId } = req.params;
// validate integration accessibility // validate integration accessibility
const integration = await Integration.findOne({ const integration = await Integration.findOne({
_id: integrationId _id: integrationId
}); });
if (!integration) { if (!integration) {
throw new Error('Failed to find integration'); return next(IntegrationNotFoundError({message: 'Failed to locate Integration'}))
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: integration.workspace.toString(),
acceptedRoles,
acceptedStatuses
});
const integrationAuth = await IntegrationAuth.findOne({
_id: integration.integrationAuth
}).select(
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
);
if (!integrationAuth) {
throw new Error('Failed to find integration authorization');
}
req.integration = integration;
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString()
});
return next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed integration authorization'
});
} }
await validateMembership({
userId: req.user._id.toString(),
workspaceId: integration.workspace.toString(),
acceptedRoles,
acceptedStatuses
});
const integrationAuth = await IntegrationAuth.findOne({
_id: integration.integrationAuth
}).select(
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
);
if (!integrationAuth) {
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}))
}
req.integration = integration;
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString()
});
return next();
}; };
}; };
@@ -3,6 +3,7 @@ import { Request, Response, NextFunction } from 'express';
import { IntegrationAuth } from '../models'; import { IntegrationAuth } from '../models';
import { IntegrationService } from '../services'; import { IntegrationService } from '../services';
import { validateMembership } from '../helpers/membership'; import { validateMembership } from '../helpers/membership';
import { UnauthorizedRequestError } from '../utils/errors';
/** /**
* Validate if user on request is a member of workspace with proper roles associated * Validate if user on request is a member of workspace with proper roles associated
@@ -22,41 +23,33 @@ const requireIntegrationAuthorizationAuth = ({
attachAccessToken?: boolean; attachAccessToken?: boolean;
}) => { }) => {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
try { const { integrationAuthId } = req.params;
const { integrationAuthId } = req.params;
const integrationAuth = await IntegrationAuth.findOne({ const integrationAuth = await IntegrationAuth.findOne({
_id: integrationAuthId _id: integrationAuthId
}).select( }).select(
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt' '+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
); );
if (!integrationAuth) { if (!integrationAuth) {
throw new Error('Failed to find integration authorization'); return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authorization credentials'}))
} }
await validateMembership({ await validateMembership({
userId: req.user._id.toString(), userId: req.user._id.toString(),
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace.toString(),
acceptedRoles, acceptedRoles,
acceptedStatuses acceptedStatuses
}); });
req.integrationAuth = integrationAuth; req.integrationAuth = integrationAuth;
if (attachAccessToken) { if (attachAccessToken) {
req.accessToken = await IntegrationService.getIntegrationAuthAccess({ req.accessToken = await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString() integrationAuthId: integrationAuth._id.toString()
});
}
return next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed (authorization) integration authorizationt'
}); });
} }
return next();
}; };
}; };
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/node';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { IOrganization, MembershipOrg } from '../models'; import { IOrganization, MembershipOrg } from '../models';
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
/** /**
* Validate if user on request is a member with proper roles for organization * Validate if user on request is a member with proper roles for organization
@@ -19,35 +19,28 @@ const requireOrganizationAuth = ({
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
// organization authorization middleware // organization authorization middleware
try { // validate organization membership
// validate organization membership const membershipOrg = await MembershipOrg.findOne({
const membershipOrg = await MembershipOrg.findOne({ user: req.user._id,
user: req.user._id, organization: req.params.organizationId
organization: req.params.organizationId }).populate<{ organization: IOrganization }>('organization');
}).populate<{ organization: IOrganization }>('organization');
if (!membershipOrg) {
throw new Error('Failed to find organization membership');
}
if (!acceptedRoles.includes(membershipOrg.role)) { if (!membershipOrg) {
throw new Error('Failed to validate organization membership role'); return next(UnauthorizedRequestError({message: "You're not a member of this Organization."}))
}
if (!acceptedStatuses.includes(membershipOrg.status)) {
throw new Error('Failed to validate organization membership status');
}
req.membershipOrg = membershipOrg;
return next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed organization authorization'
});
} }
//TODO is this important to validate? I mean is it possible to save wrong role to database or get wrong role from databse? - Zamion101
if (!acceptedRoles.includes(membershipOrg.role)) {
return next(ValidationError({message: 'Failed to validate Organization Membership Role'}))
}
if (!acceptedStatuses.includes(membershipOrg.status)) {
return next(ValidationError({message: 'Failed to validate Organization Membership Status'}))
}
req.membershipOrg = membershipOrg;
return next();
}; };
}; };
@@ -1,8 +1,8 @@
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import * as Sentry from '@sentry/node';
import { ServiceToken } from '../models'; import { ServiceToken } from '../models';
import { JWT_SERVICE_SECRET } from '../config'; import { JWT_SERVICE_SECRET } from '../config';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
declare module 'jsonwebtoken' { declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -24,33 +24,27 @@ const requireServiceTokenAuth = async (
next: NextFunction next: NextFunction
) => { ) => {
// JWT service token middleware // JWT service token middleware
try {
if (!req.headers?.authorization) const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
throw new Error('Failed to locate authorization header'); if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
//TODO: Determine what is the actual Token Type for Service Token Authentication (ex. Bearer)
//if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(UnauthorizedRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const token = req.headers.authorization.split(' ')[1]; const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, JWT_SERVICE_SECRET)
);
const decodedToken = <jwt.UserIDJwtPayload>( const serviceToken = await ServiceToken.findOne({
jwt.verify(token, JWT_SERVICE_SECRET) _id: decodedToken.serviceTokenId
); })
.populate('user', '+publicKey')
.select('+encryptedKey +publicKey +nonce');
const serviceToken = await ServiceToken.findOne({ if (!serviceToken) return next(UnauthorizedRequestError({message: 'The service token does not match the record in the database'}))
_id: decodedToken.serviceTokenId
})
.populate('user', '+publicKey')
.select('+encryptedKey +publicKey +nonce');
if (!serviceToken) throw new Error('Failed to find service token'); req.serviceToken = serviceToken;
return next();
req.serviceToken = serviceToken;
return next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed to authenticate service token'
});
}
}; };
export default requireServiceTokenAuth; export default requireServiceTokenAuth;
+16 -24
View File
@@ -1,8 +1,8 @@
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import * as Sentry from '@sentry/node';
import { User } from '../models'; import { User } from '../models';
import { JWT_SIGNUP_SECRET } from '../config'; import { JWT_SIGNUP_SECRET } from '../config';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
declare module 'jsonwebtoken' { declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -21,32 +21,24 @@ const requireSignupAuth = async (
) => { ) => {
// JWT (temporary) authentication middleware for complete signup // JWT (temporary) authentication middleware for complete signup
try { const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
if (!req.headers?.authorization) if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
throw new Error('Failed to locate authorization header'); if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, JWT_SIGNUP_SECRET)
);
const token = req.headers.authorization.split(' ')[1]; const user = await User.findOne({
const decodedToken = <jwt.UserIDJwtPayload>( _id: decodedToken.userId
jwt.verify(token, JWT_SIGNUP_SECRET) }).select('+publicKey');
);
const user = await User.findOne({ if (!user)
_id: decodedToken.userId return next(UnauthorizedRequestError({message: 'Unable to authenticate for User account completion. Try logging in again'}))
}).select('+publicKey');
if (!user) req.user = user;
throw new Error('Failed to temporarily authenticate unfound user'); return next();
req.user = user;
return next();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(401).send({
error:
'Failed to temporarily authenticate user for complete account. Try logging in'
});
}
}; };
export default requireSignupAuth; export default requireSignupAuth;
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/node';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { validateMembership } from '../helpers/membership'; import { validateMembership } from '../helpers/membership';
import { UnauthorizedRequestError } from '../utils/errors';
type req = 'params' | 'body' | 'query'; type req = 'params' | 'body' | 'query';
@@ -36,11 +36,7 @@ const requireWorkspaceAuth = ({
return next(); return next();
} catch (err) { } catch (err) {
Sentry.setUser(null); return next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
Sentry.captureException(err);
return res.status(401).send({
error: 'Failed workspace authorization'
});
} }
}; };
}; };
+3 -7
View File
@@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import * as Sentry from '@sentry/node';
import { validationResult } from 'express-validator'; import { validationResult } from 'express-validator';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
/** /**
* Validate intended inputs on [req] via express-validator * Validate intended inputs on [req] via express-validator
@@ -15,16 +15,12 @@ const validate = (req: Request, res: Response, next: NextFunction) => {
try { try {
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() }); return next(BadRequestError({context: {errors: errors.array}}))
} }
return next(); return next();
} catch (err) { } catch (err) {
Sentry.setUser(null); return next(UnauthorizedRequestError({message: 'Unauthenticated requests are not allowed. Try logging in'}))
Sentry.captureException(err);
return res.status(401).send({
error: "Looks like you're unauthenticated . Try logging in"
});
} }
}; };
-2
View File
@@ -34,6 +34,4 @@ router.get(
keyController.getLatestKey keyController.getLatestKey
); );
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
export default router; export default router;
+9 -1
View File
@@ -5,8 +5,16 @@ import {
POSTHOG_PROJECT_API_KEY, POSTHOG_PROJECT_API_KEY,
TELEMETRY_ENABLED TELEMETRY_ENABLED
} from '../config'; } from '../config';
import { getLogger } from '../utils/logger';
console.log('TELEMETRY_ENABLED: ', TELEMETRY_ENABLED); if(TELEMETRY_ENABLED){
getLogger("backend-main").info([
"",
"Infisical collects telemetry data about general usage.",
"The data helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth for investors as we support Infisical as open-source software.",
"To opt out of telemetry, you can set `TELEMETRY_ENABLED=false` within the environment variables",
].join('\n'))
}
let postHogClient: any; let postHogClient: any;
if (NODE_ENV === 'production' && TELEMETRY_ENABLED) { if (NODE_ENV === 'production' && TELEMETRY_ENABLED) {
+3 -3
View File
@@ -1,10 +1,10 @@
/* eslint-disable no-console */
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { getLogger } from '../utils/logger';
export const initDatabase = (MONGO_URL: string) => { export const initDatabase = (MONGO_URL: string) => {
mongoose mongoose
.connect(MONGO_URL) .connect(MONGO_URL)
.then(() => console.log('Successfully connected to DB')) .then(() => getLogger("database").info("Database connection established"))
.catch((e) => console.log('Failed to connect to DB ', e)); .catch((e) => getLogger("database").error(`Unable to establish Database connection due to the error.\n${e}`));
return mongoose.connection; return mongoose.connection;
}; };
+2 -2
View File
@@ -1,10 +1,10 @@
/* eslint-disable no-console */
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { createTerminus } from '@godaddy/terminus'; import { createTerminus } from '@godaddy/terminus';
import { getLogger } from '../utils/logger';
export const setUpHealthEndpoint = <T>(server: T) => { export const setUpHealthEndpoint = <T>(server: T) => {
const onSignal = () => { const onSignal = () => {
console.log('Server is starting clean-up'); getLogger('backend-main').info('Server is starting clean-up');
return Promise.all([ return Promise.all([
new Promise((resolve) => { new Promise((resolve) => {
if (mongoose.connection && mongoose.connection.readyState == 1) { if (mongoose.connection && mongoose.connection.readyState == 1) {
+19 -1
View File
@@ -1,13 +1,14 @@
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config'; import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config';
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from '../variables';
import SMTPConnection from 'nodemailer/lib/smtp-connection'; import SMTPConnection from 'nodemailer/lib/smtp-connection';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
const mailOpts: SMTPConnection.Options = { const mailOpts: SMTPConnection.Options = {
host: SMTP_HOST, host: SMTP_HOST,
secure: SMTP_SECURE as boolean,
port: SMTP_PORT as number port: SMTP_PORT as number
}; };
if (SMTP_USERNAME && SMTP_PASSWORD) { if (SMTP_USERNAME && SMTP_PASSWORD) {
mailOpts.auth = { mailOpts.auth = {
user: SMTP_USERNAME, user: SMTP_USERNAME,
@@ -15,6 +16,23 @@ if (SMTP_USERNAME && SMTP_PASSWORD) {
}; };
} }
if (SMTP_SECURE) {
switch (SMTP_HOST) {
case SMTP_HOST_SENDGRID:
mailOpts.requireTLS = true;
break;
case SMTP_HOST_MAILGUN:
mailOpts.requireTLS = true;
mailOpts.tls = {
ciphers: 'TLSv1.2'
}
break;
default:
mailOpts.secure = true;
break;
}
}
export const initSmtp = () => { export const initSmtp = () => {
const transporter = nodemailer.createTransport(mailOpts); const transporter = nodemailer.createTransport(mailOpts);
transporter transporter
+1
View File
@@ -1,5 +1,6 @@
import * as express from 'express'; import * as express from 'express';
// TODO: fix (any) types // TODO: fix (any) types
declare global { declare global {
namespace Express { namespace Express {
+116
View File
@@ -0,0 +1,116 @@
import RequestError, { LogLevel, RequestErrorContext } from "./requestError"
//* ----->[GENERAL HTTP ERRORS]<-----
export const RouteNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'route_not_found',
message: error?.message ?? 'The requested source was not found',
context: error?.context,
stack: error?.stack
})
export const MethodNotAllowedError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 405,
type: error?.type ?? 'method_not_allowed',
message: error?.message ?? 'The requested method is not allowed for the resource',
context: error?.context,
stack: error?.stack
})
export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 401,
type: error?.type ?? 'unauthorized',
message: error?.message ?? 'You are not authorized to access this resource',
context: error?.context,
stack: error?.stack
})
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 403,
type: error?.type ?? 'forbidden',
message: error?.message ?? 'You are not allowed to access this resource',
context: error?.context,
stack: error?.stack
})
export const BadRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 400,
type: error?.type ?? 'bad_request',
message: error?.message ?? 'The request is invalid or cannot be served',
context: error?.context,
stack: error?.stack
})
export const InternalServerError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 500,
type: error?.type ?? 'internal_server_error',
message: error?.message ?? 'The server encountered an error while processing the request',
context: error?.context,
stack: error?.stack
})
export const ServiceUnavailableError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 503,
type: error?.type ?? 'service_unavailable',
message: error?.message ?? 'The service is currently unavailable. Please try again later.',
context: error?.context,
stack: error?.stack
})
export const ValidationError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 400,
type: error?.type ?? 'validation_error',
message: error?.message ?? 'The request failed validation',
context: error?.context,
stack: error?.stack
})
//* ----->[INTEGRATION ERRORS]<-----
export const IntegrationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'integration_not_found_error',
message: error?.message ?? 'The requested integration was not found',
context: error?.context,
stack: error?.stack
})
//* ----->[WORKSPACE ERRORS]<-----
export const WorkspaceNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'workspace_not_found_error',
message: error?.message ?? 'The requested workspace was not found',
context: error?.context,
stack: error?.stack
})
//* ----->[ORGANIZATION ERRORS]<-----
export const OrganizationNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'organization_not_found_error',
message: error?.message ?? 'The requested organization was not found',
context: error?.context,
stack: error?.stack
})
//* ----->[ACCOUNT ERRORS]<-----
export const AccountNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'account_not_found_error',
message: error?.message ?? 'The requested account was not found',
context: error?.context,
stack: error?.stack
})
//* ----->[MISC ERRORS]<-----
+65
View File
@@ -0,0 +1,65 @@
/* eslint-disable no-console */
import { createLogger, format, transports } from 'winston';
import LokiTransport from 'winston-loki';
import { LOKI_HOST, NODE_ENV } from '../config';
const { combine, colorize, label, printf, splat, timestamp } = format;
const logFormat = (prefix: string) => combine(
timestamp(),
splat(),
label({ label: prefix }),
printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`)
);
const createLoggerWithLabel = (level: string, label: string) => {
const _level = level.toLowerCase() || 'info'
//* Always add Console output to transports
const _transports: any[] = [
new transports.Console({
format: combine(
colorize(),
logFormat(label),
// format.json()
)
})
]
//* Add LokiTransport if it's enabled
if(LOKI_HOST !== undefined){
_transports.push(
new LokiTransport({
host: LOKI_HOST,
handleExceptions: true,
handleRejections: true,
batching: true,
level: _level,
timeout: 30000,
format: format.combine(
format.json()
),
labels: {app: process.env.npm_package_name, version: process.env.npm_package_version, environment: NODE_ENV},
onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err)
})
)
}
return createLogger({
level: _level,
transports: _transports,
format: format.combine(
logFormat(label),
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] })
)
});
}
const DEFAULT_LOGGERS = {
"backend-main": createLoggerWithLabel('info', '[IFSC:backend-main]'),
"database": createLoggerWithLabel('info', '[IFSC:database]'),
}
type LoggerNames = keyof typeof DEFAULT_LOGGERS
export const getLogger = (loggerName: LoggerNames) => {
return DEFAULT_LOGGERS[loggerName]
}
+65
View File
@@ -0,0 +1,65 @@
/*
Original work Copyright (c) 2016, Nikolay Nemshilov <nemshilov@gmail.com>
Modified work Copyright (c) 2016, David Banham <david@banham.id.au>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-env node */
const Layer = require('express/lib/router/layer');
const Router = require('express/lib/router');
const last = (arr = []) => arr[arr.length - 1];
const noop = Function.prototype;
function copyFnProps(oldFn, newFn) {
Object.keys(oldFn).forEach((key) => {
newFn[key] = oldFn[key];
});
return newFn;
}
function wrap(fn) {
const newFn = function newFn(...args) {
const ret = fn.apply(this, args);
const next = (args.length === 5 ? args[2] : last(args)) || noop;
if (ret && ret.catch) ret.catch(err => next(err));
return ret;
};
Object.defineProperty(newFn, 'length', {
value: fn.length,
writable: false,
});
return copyFnProps(fn, newFn);
}
export function patchRouterParam() {
const originalParam = Router.prototype.constructor.param;
Router.prototype.constructor.param = function param(name, fn) {
fn = wrap(fn);
return originalParam.call(this, name, fn);
};
}
Object.defineProperty(Layer.prototype, 'handle', {
enumerable: true,
get() {
return this.__handle;
},
set(fn) {
fn = wrap(fn);
this.__handle = fn;
},
});
+113
View File
@@ -0,0 +1,113 @@
import { Request } from 'express'
import { VERBOSE_ERROR_OUTPUT } from '../config'
export enum LogLevel {
DEBUG = 100,
INFO = 200,
NOTICE = 250,
WARNING = 300,
ERROR = 400,
CRITICAL = 500,
ALERT = 550,
EMERGENCY = 600,
}
export type RequestErrorContext = {
logLevel?: LogLevel,
statusCode: number,
type: string,
message: string,
context?: Record<string, unknown>,
stack?: string|undefined
}
export default class RequestError extends Error{
private _logLevel: LogLevel
private _logName: string
statusCode: number
type: string
context: Record<string, unknown>
extra: Record<string, string|number|symbol>[]
private stacktrace: string|undefined|string[]
constructor(
{logLevel, statusCode, type, message, context, stack} : RequestErrorContext
){
super(message)
this._logLevel = logLevel || LogLevel.INFO
this._logName = LogLevel[this._logLevel]
this.statusCode = statusCode
this.type = type
this.context = context || {}
this.extra = []
if(stack) this.stack = stack
else Error.captureStackTrace(this, this.constructor)
this.stacktrace = this.stack?.split('\n')
}
static convertFrom(error: Error) {
//This error was not handled by error handler. Please report this incident to the staff.
return new RequestError({
logLevel: LogLevel.ERROR,
statusCode: 500,
type: 'internal_server_error',
message: 'This error was not handled by error handler. Please report this incident to the staff',
context: {
message: error.message,
name: error.name
},
stack: error.stack
})
}
get level(){ return this._logLevel }
get levelName(){ return this._logName }
withTags(...tags: string[]|number[]){
this.context['tags'] = Object.assign(tags, this.context['tags'])
return this
}
withExtras(...extras: Record<string, string|boolean|number>[]){
this.extra = Object.assign(extras, this.extra)
return this
}
private _omit(obj: any, keys: string[]): typeof obj{
const exclude = new Set(keys)
obj = Object.fromEntries(Object.entries(obj).filter(e => !exclude.has(e[0])))
return obj
}
public format(req: Request){
let _context = Object.assign({
stacktrace: this.stacktrace
}, this.context)
//* Omit sensitive information from context that can leak internal workings of this program if user is not developer
if(!VERBOSE_ERROR_OUTPUT){
_context = this._omit(_context, [
'stacktrace',
'exception',
])
}
const formatObject = {
type: this.type,
message: this.message,
context: _context,
level: this.level,
level_name: this.levelName,
status_code: this.statusCode,
datetime_iso: new Date().toISOString(),
application: process.env.npm_package_name || 'unknown',
request_id: req.headers["Request-Id"],
extra: this.extra
}
return formatObject
}
}
-5
View File
@@ -1,5 +0,0 @@
const ACTION_PUSH_TO_HEROKU = 'pushToHeroku';
export {
ACTION_PUSH_TO_HEROKU
}
+7 -6
View File
@@ -32,9 +32,9 @@ import {
GRANTED GRANTED
} from './organization'; } from './organization';
import { SECRET_SHARED, SECRET_PERSONAL } from './secret'; import { SECRET_SHARED, SECRET_PERSONAL } from './secret';
import { PLAN_STARTER, PLAN_PRO } from './stripe';
import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event'; import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event';
import { ACTION_PUSH_TO_HEROKU } from './action'; import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from './smtp';
import { PLAN_STARTER, PLAN_PRO } from './stripe';
export { export {
OWNER, OWNER,
@@ -44,8 +44,6 @@ export {
ACCEPTED, ACCEPTED,
COMPLETED, COMPLETED,
GRANTED, GRANTED,
PLAN_STARTER,
PLAN_PRO,
SECRET_SHARED, SECRET_SHARED,
SECRET_PERSONAL, SECRET_PERSONAL,
ENV_DEV, ENV_DEV,
@@ -69,6 +67,9 @@ export {
INTEGRATION_GITHUB_API_URL, INTEGRATION_GITHUB_API_URL,
EVENT_PUSH_SECRETS, EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS, EVENT_PULL_SECRETS,
ACTION_PUSH_TO_HEROKU, INTEGRATION_OPTIONS,
INTEGRATION_OPTIONS SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
PLAN_STARTER,
PLAN_PRO,
}; };
+7
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
}
+17 -4
View File
@@ -3,24 +3,37 @@ module github.com/Infisical/infisical-merge
go 1.19 go 1.19
require ( require (
github.com/99designs/keyring v1.2.2
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.3.0 golang.org/x/crypto v0.3.0
golang.org/x/term v0.3.0
) )
require ( require (
github.com/alessio/shellescape v1.4.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/go-openapi/errors v0.20.2 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
go.mongodb.org/mongo-driver v1.10.0 // indirect
golang.org/x/net v0.2.0 // indirect golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.2.0 // indirect golang.org/x/sys v0.3.0 // indirect
) )
require ( require (
github.com/go-resty/resty/v2 v2.7.0 github.com/go-resty/resty/v2 v2.7.0
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jedib0t/go-pretty v4.3.0+incompatible
github.com/manifoldco/promptui v0.9.0 github.com/manifoldco/promptui v0.9.0
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/zalando/go-keyring v0.2.1
) )
+73 -15
View File
@@ -1,5 +1,9 @@
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
@@ -10,23 +14,57 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -34,32 +72,52 @@ github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+3 -1
View File
@@ -91,7 +91,9 @@ var loginCmd = &cobra.Command{
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored) err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
if err != nil { if err != nil {
log.Errorln("Unable to store your credentials in system key ring") currentVault, _ := util.GetCurrentVaultBackend()
log.Errorf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault)
log.Errorln("To trouble shoot further, read https://infisical.com/docs/cli/faq")
log.Debugln(err) log.Debugln(err)
return return
} }
+3 -1
View File
@@ -15,7 +15,7 @@ var rootCmd = &cobra.Command{
Short: "Infisical CLI is used to inject environment variables into any process", Short: "Infisical CLI is used to inject environment variables into any process",
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`, Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
Version: "0.1.11", Version: "0.1.15",
} }
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
@@ -31,4 +31,6 @@ func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging") rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
rootCmd.PersistentFlags().StringVar(&util.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend") rootCmd.PersistentFlags().StringVar(&util.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend")
// rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
// }
} }
+12
View File
@@ -60,6 +60,13 @@ var runCmd = &cobra.Command{
return return
} }
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil {
log.Errorln("Unable to parse the secret-overriding flag")
log.Debugln(err)
return
}
shouldExpandSecrets, err := cmd.Flags().GetBool("expand") shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
if err != nil { if err != nil {
log.Errorln("Unable to parse the substitute flag") log.Errorln("Unable to parse the substitute flag")
@@ -84,6 +91,10 @@ var runCmd = &cobra.Command{
secrets = util.SubstituteSecrets(secrets) secrets = util.SubstituteSecrets(secrets)
} }
if secretOverriding {
secrets = util.OverrideWithPersonalSecrets(secrets)
}
if cmd.Flags().Changed("command") { if cmd.Flags().Changed("command") {
command := cmd.Flag("command").Value.String() command := cmd.Flag("command").Value.String()
err = executeMultipleCommandWithEnvs(command, secrets) err = executeMultipleCommandWithEnvs(command, secrets)
@@ -108,6 +119,7 @@ func init() {
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from") runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from") runCmd.Flags().String("projectId", "", "The project ID from which your secrets should be pulled from")
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets") runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets with the same name over shared secrets")
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")") runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
} }
+98
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]", wantedVaultTypeName, err)
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)
}
+5 -1
View File
@@ -1,5 +1,7 @@
package models package models
import "github.com/99designs/keyring"
type UserCredentials struct { type UserCredentials struct {
Email string `json:"email"` Email string `json:"email"`
PrivateKey string `json:"privateKey"` PrivateKey string `json:"privateKey"`
@@ -8,12 +10,14 @@ type UserCredentials struct {
// The file struct for Infisical config file // The file struct for Infisical config file
type ConfigFile struct { type ConfigFile struct {
LoggedInUserEmail string `json:"loggedInUserEmail"` LoggedInUserEmail string `json:"loggedInUserEmail"`
VaultBackendType keyring.BackendType `json:"vaultBackendType"`
} }
type SingleEnvironmentVariable struct { type SingleEnvironmentVariable struct {
Key string `json:"key"` Key string `json:"key"`
Value string `json:"value"` Value string `json:"value"`
Type string `json:"type"`
} }
type WorkspaceConfigFile struct { type WorkspaceConfigFile struct {
+1
View File
@@ -19,6 +19,7 @@ func GetHomeDir() (string, error) {
return directory, err return directory, err
} }
// write file to given path. If path does not exist throw error
func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) error { func WriteToFile(fileName string, dataToWrite []byte, filePerm os.FileMode) error {
err := os.WriteFile(fileName, dataToWrite, filePerm) err := os.WriteFile(fileName, dataToWrite, filePerm)
if err != nil { if err != nil {
+66
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{ configFile := models.ConfigFile{
LoggedInUserEmail: userCredentials.Email, LoggedInUserEmail: userCredentials.Email,
VaultBackendType: existingConfigFile.VaultBackendType,
} }
configFileMarshalled, err := json.Marshal(configFile) configFileMarshalled, err := json.Marshal(configFile)
@@ -152,3 +159,62 @@ func GetAllWorkSpaceConfigsStartingFromCurrentPath() (workspaces []models.Worksp
return listOfWorkSpaceConfigs, nil return listOfWorkSpaceConfigs, nil
} }
// Get the infisical config file and if it doesn't exist, return empty config model, otherwise raise error
func GetConfigFile() (models.ConfigFile, error) {
fullConfigFilePath, _, err := GetFullConfigFilePath()
if err != nil {
return models.ConfigFile{}, err
}
configFileAsBytes, err := os.ReadFile(fullConfigFilePath)
if err != nil {
if err, ok := err.(*os.PathError); ok {
return models.ConfigFile{}, nil
} else {
return models.ConfigFile{}, err
}
}
var configFile models.ConfigFile
err = json.Unmarshal(configFileAsBytes, &configFile)
if err != nil {
return models.ConfigFile{}, err
}
return configFile, nil
}
// Write a ConfigFile to disk. Raise error if unable to save the model to ask
func WriteConfigFile(configFile *models.ConfigFile) error {
fullConfigFilePath, fullConfigFileDirPath, 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)
}
// check if config folder exists and if not create it
if _, err := os.Stat(fullConfigFileDirPath); errors.Is(err, os.ErrNotExist) {
err := os.Mkdir(fullConfigFileDirPath, os.ModePerm)
if err != nil {
return err
}
}
// Create file in directory
err = os.WriteFile(fullConfigFilePath, configFileMarshalled, os.ModePerm)
if err != nil {
return fmt.Errorf("writeConfigFile: Unable to write to file [err=%s]", err)
}
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
}
+30 -25
View File
@@ -3,12 +3,11 @@ package util
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"github.com/99designs/keyring"
"github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/zalando/go-keyring"
) )
const SERVICE_NAME = "infisical" const SERVICE_NAME = "infisical"
@@ -17,32 +16,48 @@ const SERVICE_NAME = "infisical"
func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error { func StoreUserCredsInKeyRing(userCred *models.UserCredentials) error {
userCredMarshalled, err := json.Marshal(userCred) userCredMarshalled, err := json.Marshal(userCred)
if err != nil { if err != nil {
return fmt.Errorf("Something went wrong when marshalling user creds:", err) return fmt.Errorf("StoreUserCredsInKeyRing: something went wrong when marshalling user creds [err=%s]", err)
} }
err = keyring.Set(SERVICE_NAME, userCred.Email, string(userCredMarshalled)) // Get keyring
configuredKeyring, err := GetKeyRing()
if err != nil { if err != nil {
return fmt.Errorf("Unable to store user credentials:", err) return fmt.Errorf("StoreUserCredsInKeyRing: unable to get keyring instance with [err=%s]", err)
}
err = configuredKeyring.Set(keyring.Item{
Key: userCred.Email,
Data: []byte(string(userCredMarshalled)),
})
if err != nil {
return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err)
} }
return err return err
} }
func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) { func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentials, err error) {
credentialsString, err := keyring.Get(SERVICE_NAME, userEmail) // Get keyring
configuredKeyring, err := GetKeyRing()
if err != nil { if err != nil {
return models.UserCredentials{}, fmt.Errorf("Unable to get key from Keyring:", err) return models.UserCredentials{}, fmt.Errorf("GetUserCredsFromKeyRing: unable to get keyring instance with [err=%s]", err)
}
credentialsValue, err := configuredKeyring.Get(userEmail)
if err != nil {
return models.UserCredentials{}, fmt.Errorf("GetUserCredsFromKeyRing: 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=%s]", err)
} }
var userCredentials models.UserCredentials var userCredentials models.UserCredentials
err = json.Unmarshal([]byte(credentialsString), &userCredentials) err = json.Unmarshal([]byte(credentialsValue.Data), &userCredentials)
if err != nil { if err != nil {
return models.UserCredentials{}, fmt.Errorf("Something went wrong when unmarshalling user creds:", err) return models.UserCredentials{}, fmt.Errorf("getUserCredsFromKeyRing: Something went wrong when unmarshalling user creds [err=%s]", err)
} }
if err != nil { if err != nil {
return models.UserCredentials{}, fmt.Errorf("Unable to store user credentials", err) return models.UserCredentials{}, fmt.Errorf("GetUserCredsFromKeyRing: Unable to store user credentials [err=%s]", err)
} }
return userCredentials, err return userCredentials, err
@@ -50,23 +65,13 @@ func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentia
func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) { func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) {
if ConfigFileExists() { if ConfigFileExists() {
fullConfigFilePath, _, err := GetFullConfigFilePath() configFile, err := GetConfigFile()
if err != nil { if err != nil {
log.Debugln("Error gettting full path:", err) return false, "", fmt.Errorf("IsUserLoggedIn: unable to get logged in user from config file [err=%s]", err)
return false, "", err
} }
configFileAsBytes, err := os.ReadFile(fullConfigFilePath) if configFile.LoggedInUserEmail == "" {
if err != nil { return false, "", nil
log.Debugln("Unable to read config file:", err)
return false, "", err
}
var configFile models.ConfigFile
err = json.Unmarshal(configFileAsBytes, &configFile)
if err != nil {
log.Debugln("Unable to unmarshal config file:", err)
return false, "", err
} }
userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail) userCreds, err := GetUserCredsFromKeyRing(configFile.LoggedInUserEmail)
@@ -89,7 +94,7 @@ func IsUserLoggedIn() (hasUserLoggedIn bool, theUsersEmail string, err error) {
if response.StatusCode() > 299 { if response.StatusCode() > 299 {
log.Infoln("Login expired, please login again.") log.Infoln("Login expired, please login again.")
return false, "", fmt.Errorf("Login expired, please login again.") return false, "", fmt.Errorf("GetUserCredsFromKeyRing: Login expired, please login again.")
} }
return true, configFile.LoggedInUserEmail, nil return true, configFile.LoggedInUserEmail, nil
+44
View File
@@ -14,6 +14,9 @@ import (
"golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/box"
) )
const PERSONAL_SECRET_TYPE_NAME = "personal"
const SHARED_SECRET_TYPE_NAME = "shared"
func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, workspace models.WorkspaceConfigFile, userCreds models.UserCredentials) (listOfSecrets []models.SingleEnvironmentVariable, err error) { func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string, workspace models.WorkspaceConfigFile, userCreds models.UserCredentials) (listOfSecrets []models.SingleEnvironmentVariable, err error) {
var pullSecretsRequestResponse models.PullSecretsResponse var pullSecretsRequestResponse models.PullSecretsResponse
response, err := httpClient. response, err := httpClient.
@@ -78,6 +81,7 @@ func getSecretsByWorkspaceIdAndEnvName(httpClient resty.Client, envName string,
env := models.SingleEnvironmentVariable{ env := models.SingleEnvironmentVariable{
Key: string(plainTextKey), Key: string(plainTextKey),
Value: string(plainTextValue), Value: string(plainTextValue),
Type: string(secret.Type),
} }
listOfEnv = append(listOfEnv, env) listOfEnv = append(listOfEnv, env)
@@ -187,6 +191,7 @@ func GetSecretsFromAPIUsingInfisicalToken(infisicalToken string, envName string,
env := models.SingleEnvironmentVariable{ env := models.SingleEnvironmentVariable{
Key: string(plainTextKey), Key: string(plainTextKey),
Value: string(plainTextValue), Value: string(plainTextValue),
Type: string(secret.Type),
} }
listOfEnv = append(listOfEnv, env) listOfEnv = append(listOfEnv, env)
@@ -335,9 +340,48 @@ func SubstituteSecrets(secrets []models.SingleEnvironmentVariable) []models.Sing
expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{ expandedSecrets = append(expandedSecrets, models.SingleEnvironmentVariable{
Key: secret.Key, Key: secret.Key,
Value: expandedVariable, Value: expandedVariable,
Type: secret.Type,
}) })
} }
return expandedSecrets return expandedSecrets
} }
// if two secrets with the same name are found, the one that has type `personal` will be in the returned list
func OverrideWithPersonalSecrets(secrets []models.SingleEnvironmentVariable) []models.SingleEnvironmentVariable {
personalSecret := make(map[string]models.SingleEnvironmentVariable)
sharedSecret := make(map[string]models.SingleEnvironmentVariable)
secretsToReturn := []models.SingleEnvironmentVariable{}
for _, secret := range secrets {
if secret.Type == PERSONAL_SECRET_TYPE_NAME {
personalSecret[secret.Key] = models.SingleEnvironmentVariable{
Key: secret.Key,
Value: secret.Value,
Type: secret.Type,
}
}
if secret.Type == SHARED_SECRET_TYPE_NAME {
sharedSecret[secret.Key] = models.SingleEnvironmentVariable{
Key: secret.Key,
Value: secret.Value,
Type: secret.Type,
}
}
}
for _, secret := range secrets {
personalValue, personalExists := personalSecret[secret.Key]
sharedValue, sharedExists := sharedSecret[secret.Key]
if personalExists && sharedExists || personalExists && !sharedExists {
secretsToReturn = append(secretsToReturn, personalValue)
} else {
secretsToReturn = append(secretsToReturn, sharedValue)
}
}
return secretsToReturn
}
+66
View File
@@ -0,0 +1,66 @@
package util
import (
"fmt"
"os"
"github.com/99designs/keyring"
"golang.org/x/term"
)
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 == "" {
return keyring.AvailableBackends()[0], nil
}
return configFile.VaultBackendType, nil
}
func GetKeyRing() (keyring.Keyring, error) {
currentVaultBackend, err := GetCurrentVaultBackend()
if err != nil {
return nil, fmt.Errorf("GetKeyRing: 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 {
return nil, fmt.Errorf("GetKeyRing: Unable to create instance of Keyring because of [err=%s]", err)
}
return keyringInstance, nil
}
func fileKeyringPassphrasePrompt(prompt string) (string, error) {
if password, ok := os.LookupEnv("INFISICAL_VAULT_FILE_PASSPHRASE"); ok {
return password, nil
}
fmt.Fprintf(os.Stderr, "%s: ", prompt)
b, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
return string(b), nil
}
+35
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()
}
+1 -1
View File
@@ -9,7 +9,7 @@ title: "Commands"
| `login` | Used to authenticate and set the logged in user. | | `login` | Used to authenticate and set the logged in user. |
| `init` | Used to link a local project to the platform. | | `init` | Used to link a local project to the platform. |
| `run` | Used to inject envars from the platform into an application process. | | `run` | Used to inject envars from the platform into an application process. |
| `vault` | Used to manage where your login credentials are stored at rest |
## Global options ## Global options
| Option | Description | | Option | Description |
+2 -4
View File
@@ -7,7 +7,5 @@ infisical login
``` ```
## Description ## Description
The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI.
Verify a user and save credentials to the system keyring. If you want to change where the login credentials are stored, visit the [vaults command](./vault)
To change the logged in user, run the command again to overwrite the previous login.
+1
View File
@@ -34,3 +34,4 @@ Inject environment variables from the platform into an application process.
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | None | | `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | None |
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` | | `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
| `--command` | Pass secrets into chained commands (e.g., `"first-command && second-command; more-commands..."`) | None | | `--command` | Pass secrets into chained commands (e.g., `"first-command && second-command; more-commands..."`) | None |
| `--secret-overriding`| Prioritizes personal secrets with the same name over shared secrets | `true` |
+50
View File
@@ -0,0 +1,50 @@
---
title: "infisical vault"
---
<Tabs>
<Tab title="View current Vault">
```bash
infisical vault
# Example output
The following vaults are available on your system:
- keychain
- pass
- file
You are currently using [keychain] vault to store your login credentials
```
</Tab>
<Tab title="Switch vault">
```bash
infisical vault set <name-of-vault>
# Example
infisical vault set keychain
```
</Tab>
</Tabs>
## Description
To ensure secure storage of your login credentials when using the CLI, Infisical stores login credentials securely in a system vault or encrypted text file with a passphrase known only by the user.
<Accordion title="Supported vaults">
By default, the most appropriate vault is chosen to store your login credentials.
For example, if you are on macOS, KeyChain will be automatically selected.
- [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac)
- [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager)
- Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5))
- [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)
- [Pass](https://www.passwordstore.org/)
- [KeyCtl]()
- Encrypted file (JWT)
</Accordion>
<Tip>To avoid constantly entering your passphrase when using the `file` vault type, set the `INFISICAL_VAULT_FILE_PASSPHRASE` environment variable with your password in your shell</Tip>
+15
View File
@@ -0,0 +1,15 @@
---
title: "FAQ"
---
Frequently asked questions about the CLI can be found on this page.
If you can't find the answer you're looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="I'm getting a Keyring related error message when trying to login" defaultOpen="true">
By default, the CLI will choose the most suitable store available on your system.
If you experience issues with the default store, you can switch to a different one.
If none of the available stores work for you, you can try using the `file` store type by running `infisical vault set file`, which should work in most cases.
If you are still experiencing trouble, please seek support.
[Learn more about vault command](./commands/vault)
</Accordion>
+19 -8
View File
@@ -4,10 +4,27 @@ title: "Usage"
Prerequisite: [Install the CLI](/cli/overview) Prerequisite: [Install the CLI](/cli/overview)
## Authenticate
<Tabs>
<Tab title="Local development">
To use the Infisical CLI in your development environment, you can run the command below.
This will allow you to access the features and functionality provided by the CLI.
```bash
infisical login
```
</Tab>
<Tab title="Infisical Token">
To use Infisical CLI in environments where you cannot run the `infisical login` command, you can authenticate via a
Infisical Token instead. Learn more about [Infisical Token](../getting-started/dashboard/token).
</Tab>
</Tabs>
## Initialize Infisical for your project ## Initialize Infisical for your project
```bash ```bash
# move to your project # navigate to your project
cd /path/to/project cd /path/to/project
# initialize infisical # initialize infisical
@@ -21,13 +38,7 @@ infisical init
infisical run -- [your application start command] infisical run -- [your application start command]
``` ```
Options you can specify: View all available options for `run` command [here](./commands/run)
| Option | Description | Default value |
| ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
| `--projectId` | Used to link a local project to the platform (required only if injecting via the service token method) | `None` |
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
## Examples: ## Examples:
-18
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`.
+23 -46
View File
@@ -16,59 +16,44 @@ cd infisical
## Set up environment variables ## Set up environment variables
Before running the docker-compose we have to generate the .env file with the environment variables, you can create your own file or start with the Start by creating a .env file at the root of the Infisical directory. It's best to start with the provided [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) template containing the necessary envars to fill out your .env file — you only have to modify the SMTP parameters.
`.env.example` as an example guide.
Mandatory variables in the `.env` file: <Warning>
The pre-populated environment variable values in the `.env.example` file are meant to be used in development only.
You'll want to fill in your own values in production, especially concerning encryption keys, secrets, and SMTP parameters.
</Warning>
1. Keys and JWT variables Refer to the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) for guidance on each envar.
![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:
``` - `ENCRYPTION_KEY` should be a [32-byte random hex](https://www.browserling.com/tools/random-hex)
PRIVATE_KEY='oGVv5rThrpZ7WLgQW27chY1cXngr4wLQIZnGfSKgHPk=' - `MONGO_URL` should take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`.
PUBLIC_KEY='ldr6JaC7AY+tun3omGLdE4SWpkJbtVBOI54KfUP53Xc=' </Tip>
```
2. Mongo variables and site URL <Tip>
Bring and configure your own SMTP server by following our [email configuration guide](https://infisical.com/docs/self-hosting/configuration/email) (we recommend using either SendGrid or Mailgun).
![image](https://user-images.githubusercontent.com/118568289/206792171-3376e3c6-c3ac-4d5d-8776-d78ee089b520.png) Alternatively, you can use the provided development (Mailhog) SMTP server to send and browse emails sent by the backend on http://localhost:8025; to use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` below.
</Tip>
These variables are used to connect the MongoDB and set the URL for the localhost.
For development, you can use `root` for the `MONGO_USERNAME` and `example` for the `MONGO_PASSWORD` as shown in the screenshot.
Take into account that if you use your own `MONGO_USERNAME` and `MONGO_PASSWORD`, you also have to change the `MONGO_URL` with the form of `MONGO_USERNAME:MONGO_PASSWORD` after the `//` part of the URL.
3. Mail SMTP service variables
![image](https://user-images.githubusercontent.com/118568289/206792653-ba3211d1-1071-43f2-93a7-8b408bbd9e0e.png)
If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following.
For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification.
For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable.
Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this:
``` ```
SMTP_HOST=smtp-server SMTP_HOST=smtp-server
SMTP_PORT=1025 SMTP_PORT=1025
SMTP_NAME=<whatever you like> SMTP_[email protected]
SMTP_FROM_NAME=Infisical
[email protected] [email protected]
SMTP_PASSWORD= SMTP_PASSWORD=
``` ```
Make sure to leave the `SMTP_PASSWORD` blank so the backend will be able to connect to MailHog <Warning>
If using Mailhog, make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog.
You can browse `http://localhost:8025/` to browse email messages sent by the backend. </Warning>
With these environment variables, you will be ready to run the docker-compose.
## Docker for development ## Docker for development
@@ -84,12 +69,4 @@ Then browse http://localhost:8080
docker-compose -f docker-compose.dev.yml down docker-compose -f docker-compose.dev.yml down
# start services # start services
docker-compose -f docker-compose.dev.yml up docker-compose -f docker-compose.dev.yml up
``` ```
The docker-compose development environment consists of:
- nginx
- frontend
- backend
- mongo
- mongo-express
+6
View File
@@ -11,6 +11,12 @@ To generate the the token, head over to your project settings as shown below.
![token add](../../images/project-token-add.png) ![token add](../../images/project-token-add.png)
## Feeding Infisical Token to the CLI
The Infisical CLI checks for the presence of an environment variable called `INFISICAL_TOKEN`.
If it detects this variable in the terminal where it is being run, it will use it to authenticate and retrieve the environment variables that the token is authorized to access.
This allows you to use the CLI in environments where you are unable to run the `infisical login` command.
<Note> <Note>
The token grants read-only access to a particular environment and project for The token grants read-only access to a particular environment and project for
a specified amount of time. Once the token is expired, the CLI using it will no longer be able to make a specified amount of time. Once the token is expired, the CLI using it will no longer be able to make
+1 -1
View File
@@ -8,7 +8,7 @@ Note that the Infisical CLI is platform-agnostic and can inject environment vari
## Set up Infisical Cloud ## Set up Infisical Cloud
1. Login or create an accout at `app.infisical.com`. 1. Login or create an account at `app.infisical.com`.
2. Create a new project. 2. Create a new project.
3. Populate your environment variables as in the image below. 3. Populate your environment variables as in the image below.
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

+34
View File
@@ -0,0 +1,34 @@
---
title: "GitHub Actions"
---
<Warning>
Infisical can sync secrets to GitHub repo secrets only. If your repo uses environment secrets, then stay tuned with this [issue](https://github.com/Infisical/infisical/issues/54).
</Warning>
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Ensure you have admin privileges to the repo you want to sync secrets to.
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for GitHub
Press on the GitHub tile and grant Infisical access to your GitHub account (repo privileges only).
![integrations github authorization](../../images/integrations-github-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which GitHub repo and press start integration to start syncing secrets to the repo.
![integrations github](../../images/integrations-github.png)
+2 -1
View File
@@ -13,7 +13,8 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available | | [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
| [Heroku](/integrations/cloud/heroku) | Cloud | Available | | [Heroku](/integrations/cloud/heroku) | Cloud | Available |
| [Vercel](/integrations/cloud/vercel) | Cloud | Available | | [Vercel](/integrations/cloud/vercel) | Cloud | Available |
| [Netlify](/integrations/cloud/netlify) | Cloud | Available | | [Netlify](/integrations/cloud/netlify) | Cloud | Available |
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
| [React](/integrations/frameworks/react) | Framework | Available | | [React](/integrations/frameworks/react) | Framework | Available |
| [Vue](/integrations/frameworks/vue) | Framework | Available | | [Vue](/integrations/frameworks/vue) | Framework | Available |
| [Express](/integrations/frameworks/express) | Framework | Available | | [Express](/integrations/frameworks/express) | Framework | Available |
+24 -17
View File
@@ -94,26 +94,31 @@
"cli/commands/login", "cli/commands/login",
"cli/commands/init", "cli/commands/init",
"cli/commands/run", "cli/commands/run",
"cli/commands/export" "cli/commands/export",
"cli/commands/vault"
] ]
} },
"cli/faq"
] ]
}, },
{ {
"group": "Self-hosting", "group": "Self-hosting",
"pages": [ "pages": [
"self-hosting/overview", "self-hosting/overview"
{ ]
"group": "Deployments options", },
"pages": [ {
"self-hosting/deployments/linux", "group": "Deployment options",
"self-hosting/deployments/kubernetes" "pages": [
] "self-hosting/deployments/linux",
}, "self-hosting/deployments/kubernetes"
{ ]
"group": "Configuration", },
"pages": ["self-hosting/configuration/envars"] {
} "group": "Configuration",
"pages": [
"self-hosting/configuration/envars",
"self-hosting/configuration/email"
] ]
}, },
{ {
@@ -140,7 +145,10 @@
}, },
{ {
"group": "CI/CD", "group": "CI/CD",
"pages": ["integrations/cicd/circleci"] "pages": [
"integrations/cicd/githubactions",
"integrations/cicd/circleci"
]
}, },
{ {
"group": "Frameworks", "group": "Frameworks",
@@ -174,8 +182,7 @@
"pages": [ "pages": [
"contributing/overview", "contributing/overview",
"contributing/code-of-conduct", "contributing/code-of-conduct",
"contributing/developing", "contributing/developing"
"contributing/FAQ"
] ]
} }
], ],
+75
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. [email protected])
- `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. [email protected]).
- `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
[email protected] # 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
[email protected] # obtained from credentials page
SMTP_PASSWORD=password # obtained from credentials page
SMTP_PORT=587
SMTP_SECURE=true
[email protected] # 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>
+16 -15
View File
@@ -3,18 +3,15 @@ title: "Environment Variables"
description: "" description: ""
--- ---
## The .env file Configuring Infisical requires setting some environment variables. There is a file called [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) at the root directory of our main repo that you can use to create a `.env` file before you start the server.
Configuring Infisical requires setting some environment variables. There is a file called `.env.example` at the root directory of our main repo that you can use to create a `.env` before you start the server.
| Variable | Description | Default Value | | Variable | Description | Default Value |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- | | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- |
| `PRIVATE_KEY` | ❗️ NaCl-generated server secret key | `None` |
| `PUBLIC_KEY` | ❗️ NaCl-generated server public key | `None` |
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` | | `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` | | `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` | | `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` | | `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` | | `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` | | `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` |
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` | | `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
@@ -23,17 +20,21 @@ Configuring Infisical requires setting some environment variables. There is a fi
| `MONGO_USERNAME` | MongoDB username if using container | `None` | | `MONGO_USERNAME` | MongoDB username if using container | `None` |
| `MONGO_PASSWORD` | MongoDB password if using container | `None` | | `MONGO_PASSWORD` | MongoDB password if using container | `None` |
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` | | `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
| `SMTP_HOST` | Hostname to connect to for establishing SMTP connections | `smtp.gmail.com` | | `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` |
| `SMTP_SECURE` | Use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
| `SMTP_PORT` | ❗️ Port to connect to for establishing SMTP connections | `587` |
| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `[email protected]`) | `None` |
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `[email protected]`) | `None` | | `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `[email protected]`) | `None` |
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` | | `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
| `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. `[email protected]`) | `None` |
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` | | `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
| `CLIENT_ID_VERCEL` | OAuth client id for Vercel integration | `None` | | `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
| `CLIENT_ID_NETLIFY` | OAuth client id for Netlify integration | `None` | | `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
| `CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` | | `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
| `CLIENT_SECRET_VERCEL` | OAuth client secret for Vercel integration | `None` | | `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
| `CLIENT_SECRET_NETLIFY` | OAuth client secret for Netlify integration | `None` | | `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` |
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` |
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` |
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` |
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` |
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` | | `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
+2 -3
View File
@@ -42,7 +42,7 @@ that by adding the `--namespace <namespace-to-install-to>` to your `helm install
```bash ```bash
## Installs to default namespace ## Installs to default namespace
helm install infisical-helm-charts/infisical --values <path to the values.yaml you downloaded/created in step 2> helm install infisical-helm-charts/infisical --generate-name --values <path to the values.yaml you downloaded/created in step 2>
``` ```
<Note> <Note>
@@ -50,5 +50,4 @@ If you have not filled out all of the required environment variables, you will s
do so. do so.
</Note> </Note>
4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`. #### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
Note: Please allow an additional time (2 minutes) for the frontend pods to be fully ready.
+3 -2
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import { memo,useState } from 'react';
import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; import { faCircle, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -96,6 +96,7 @@ const InputField = (
/> />
{props.label?.includes('Password') && ( {props.label?.includes('Password') && (
<button <button
type="button"
onClick={() => { onClick={() => {
setPasswordVisible(!passwordVisible); setPasswordVisible(!passwordVisible);
}} }}
@@ -139,4 +140,4 @@ const InputField = (
} }
}; };
export default React.memo(InputField); export default memo(InputField);
+3 -1
View File
@@ -1,4 +1,4 @@
import React from "react"; import React, { ButtonHTMLAttributes } from "react";
import Image from "next/image"; import Image from "next/image";
import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { import {
@@ -18,6 +18,7 @@ type ButtonProps = {
active?: boolean; active?: boolean;
iconDisabled?: IconProp; iconDisabled?: IconProp;
textDisabled?: string; textDisabled?: string;
type?: ButtonHTMLAttributes<any>['type'];
}; };
/** /**
@@ -92,6 +93,7 @@ export default function Button(props: ButtonProps): JSX.Element {
const button = ( const button = (
<button <button
disabled={!activityStatus} disabled={!activityStatus}
type={props.type}
onClick={props.onButtonPressed} onClick={props.onButtonPressed}
className={styleButton} className={styleButton}
> >
@@ -209,7 +209,7 @@ const AddServiceTokenDialog = ({
</div> </div>
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-36"> <div className="flex justify-end items-center bg-white/[0.07] text-base mt-2 mr-2 rounded-md text-gray-400 w-full h-44">
<input <input
type="text" type="text"
value={serviceToken} value={serviceToken}
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons'; import { faX } from '@fortawesome/free-solid-svg-icons';
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { faX } from '@fortawesome/free-solid-svg-icons'; import { faX } from '@fortawesome/free-solid-svg-icons';
@@ -1,4 +1,4 @@
import React, { SyntheticEvent, useRef } from 'react'; import { memo, SyntheticEvent, useRef } from 'react';
import { faCircle } from '@fortawesome/free-solid-svg-icons'; import { faCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -163,4 +163,4 @@ function inputPropsAreEqual(prev: DashboardInputFieldProps, next: DashboardInput
return prev.value === next.value && prev.type === next.type && prev.position === next.position && prev.blurred === next.blurred && prev.override === next.override && prev.isDuplicate === next.isDuplicate; return prev.value === next.value && prev.type === next.type && prev.position === next.position && prev.blurred === next.blurred && prev.override === next.override && prev.isDuplicate === next.isDuplicate;
} }
export default React.memo(DashboardInputField, inputPropsAreEqual); export default memo(DashboardInputField, inputPropsAreEqual);
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { import {
faArrowRight, faArrowRight,
@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react/jsx-key */ /* eslint-disable react/jsx-key */
import React, { Fragment, useEffect, useState } from 'react'; import { Fragment, useEffect, useState } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons'; import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons';
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { import {
faAngleRight, faAngleRight,
+28
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:[email protected]">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>
);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import { useEffect } from "react";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react'; import { Fragment, useCallback, useEffect, useState } from 'react';
import Head from 'next/head'; import Head from 'next/head';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import { useEffect } from "react";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
const queryString = require("query-string"); const queryString = require("query-string");
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { IconProp } from '@fortawesome/fontawesome-svg-core';
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
export default function Home() { export default function Home() {
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
+68 -59
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Head from 'next/head'; import Head from 'next/head';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
@@ -38,6 +38,10 @@ export default function Login() {
* This function check if the user entered the correct credentials and should be allowed to log in. * This function check if the user entered the correct credentials and should be allowed to log in.
*/ */
const loginCheck = async () => { const loginCheck = async () => {
if (!email || !password) {
return;
}
setIsLoading(true); setIsLoading(true);
await attemptLogin( await attemptLogin(
email, email,
@@ -45,7 +49,7 @@ export default function Login() {
setErrorLogin, setErrorLogin,
router, router,
false, false,
true true,
).then(() => { ).then(() => {
setTimeout(function () { setTimeout(function () {
setIsLoading(false); setIsLoading(false);
@@ -75,68 +79,73 @@ export default function Login() {
/> />
</div> </div>
</Link> </Link>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl"> <form
<p className="text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6"> onChange={() => setErrorLogin(false)} onSubmit={(e) => e.preventDefault()}
Log in to your account >
</p> <div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28"> <p className="text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
<InputField Log in to your account
label="Email" </p>
onChangeHandler={setEmail} <div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
type="email" <InputField
value={email} label="Email"
placeholder="" onChangeHandler={setEmail}
isRequired type="email"
autoComplete="username" value={email}
/> placeholder=""
</div> isRequired
<div className="relative flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28"> autoComplete="username"
<InputField
label="Password"
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
autoComplete="current-password"
id="current-password"
/>
<div className="absolute top-2 right-3 text-primary-700 hover:text-primary duration-200 cursor-pointer text-sm">
<Link href="/verify-email">Forgot password?</Link>
</div>
</div>
{errorLogin && <Error text="Your email and/or password are wrong." />}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
text="Log In"
onButtonPressed={loginCheck}
loading={isLoading}
size="lg"
/> />
</div> </div>
</div> <div className="relative flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28"> <InputField
label="Password"
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
autoComplete="current-password"
id="current-password"
/>
<div className="absolute top-2 right-3 text-primary-700 hover:text-primary duration-200 cursor-pointer text-sm">
<Link href="/verify-email">Forgot password?</Link>
</div>
</div>
{!isLoading && errorLogin && <Error text="Your email and/or password are wrong." />}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
type="submit"
text="Log In"
onButtonPressed={loginCheck}
loading={isLoading}
size="lg"
/>
</div>
</div>
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
<p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p> <p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p>
</div> */} </div> */}
</div>
{false && (
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
We are experiencing minor technical difficulties. We are working on
solving it right now. Please come back in a few minutes.
</div> </div>
)} {false && (
<div className="flex flex-row items-center justify-center md:pb-4 mt-4"> <div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<p className="text-sm flex justify-center text-gray-400 w-max"> <FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
Need an Infisical account? We are experiencing minor technical difficulties. We are working on
</p> solving it right now. Please come back in a few minutes.
<Link href="/signup"> </div>
<button className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"> )}
Sign up here. <div className="flex flex-row items-center justify-center md:pb-4 mt-4">
</button> <p className="text-sm flex justify-center text-gray-400 w-max">
</Link> Need an Infisical account?
</div> </p>
<Link href="/signup">
<button className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5">
Sign up here.
</button>
</Link>
</div>
</form>
</div> </div>
); );
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import { useEffect } from "react";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
const queryString = require("query-string"); const queryString = require("query-string");
+19 -10
View File
@@ -1,21 +1,30 @@
import React from "react"; import React from "react";
import Image from "next/image";
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons"; import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function NoProjects() { export default function NoProjects() {
return ( return (
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto"> <div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
<FontAwesomeIcon <div
icon={faFolderOpen} className="mb-4 mr-16"
className="text-7xl mb-8 w-full px-auto" >
/> <Image
<div className="max-w-md"> src="/images/dragon-cant-find.png"
You are not part of any projects in this organization yet. When you do, height={270}
they will appear here. width={436}
alt="google logo"
></Image>
</div> </div>
<div className="max-w-md mt-4"> <div className="p-4 rounded-md bg-bunker-500 mb-8 text-bunker-300 shadow-xl">
Create a new project, or ask other organization members to give you <div className="max-w-md">
neccessary permissions. You are not part of any projects in this organization yet. When you do,
they will appear here.
</div>
<div className="max-w-md mt-4">
Create a new project, or ask other organization members to give you
neccessary permissions.
</div>
</div> </div>
</div> </div>
); );
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import { useState } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import Plan from "~/components/billing/Plan"; import Plan from "~/components/billing/Plan";
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { import {
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
+80 -62
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import ReactCodeInput from 'react-code-input'; import ReactCodeInput from 'react-code-input';
import Head from 'next/head'; import Head from 'next/head';
import Image from 'next/image'; import Image from 'next/image';
@@ -40,8 +40,8 @@ const props = {
backgroundColor: '#0d1117', backgroundColor: '#0d1117',
color: 'white', color: 'white',
border: '1px solid gray', border: '1px solid gray',
textAlign: 'center' textAlign: 'center',
} },
} as const; } as const;
const propsPhone = { const propsPhone = {
inputStyle: { inputStyle: {
@@ -56,8 +56,8 @@ const propsPhone = {
backgroundColor: '#0d1117', backgroundColor: '#0d1117',
color: 'white', color: 'white',
border: '1px solid gray', border: '1px solid gray',
textAlign: 'center' textAlign: 'center',
} },
} as const; } as const;
export default function SignUp() { export default function SignUp() {
@@ -81,6 +81,7 @@ export default function SignUp() {
const router = useRouter(); const router = useRouter();
const [errorLogin, setErrorLogin] = useState(false); const [errorLogin, setErrorLogin] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const [verificationToken, setVerificationToken] = useState(''); const [verificationToken, setVerificationToken] = useState('');
const [backupKeyIssued, setBackupKeyIssued] = useState(false); const [backupKeyIssued, setBackupKeyIssued] = useState(false);
@@ -148,7 +149,8 @@ export default function SignUp() {
} }
}; };
// Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the criteria. // Verifies if the imformation that the users entered (name, workspace) is there, and if the password matched the
// criteria.
const signupErrorCheck = async () => { const signupErrorCheck = async () => {
setIsLoading(true); setIsLoading(true);
let errorCheck = false; let errorCheck = false;
@@ -169,7 +171,7 @@ export default function SignUp() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: errorCheck currentErrorCheck: errorCheck,
}); });
if (!errorCheck) { if (!errorCheck) {
@@ -186,8 +188,8 @@ export default function SignUp() {
.slice(0, 32) .slice(0, 32)
.padStart( .padStart(
32 + (password.slice(0, 32).length - new Blob([password]).size), 32 + (password.slice(0, 32).length - new Blob([password]).size),
'0' '0',
) ),
}) as { ciphertext: string; iv: string; tag: string }; }) as { ciphertext: string; iv: string; tag: string };
localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY); localStorage.setItem('PRIVATE_KEY', PRIVATE_KEY);
@@ -195,7 +197,7 @@ export default function SignUp() {
client.init( client.init(
{ {
username: email, username: email,
password: password password: password,
}, },
async () => { async () => {
client.createVerifier( client.createVerifier(
@@ -204,14 +206,14 @@ export default function SignUp() {
email, email,
firstName, firstName,
lastName, lastName,
organizationName: firstName + "'s organization", organizationName: firstName + '\'s organization',
publicKey: PUBLIC_KEY, publicKey: PUBLIC_KEY,
ciphertext, ciphertext,
iv, iv,
tag, tag,
salt: result.salt, salt: result.salt,
verifier: result.verifier, verifier: result.verifier,
token: verificationToken token: verificationToken,
}); });
// if everything works, go the main dashboard page. // if everything works, go the main dashboard page.
@@ -230,71 +232,84 @@ export default function SignUp() {
setErrorLogin, setErrorLogin,
router, router,
true, true,
false false,
); );
incrementStep(); incrementStep();
} catch (error) { } catch (error) {
setIsLoading(false); setIsLoading(false);
} }
} }
} },
); );
} },
); );
} else { } else {
setIsLoading(false); setIsLoading(false);
} }
}; };
const resendVerificationEmail = async () => {
setIsResendingVerificationEmail(true);
setIsLoading(true);
await sendVerificationEmail(email);
setTimeout(() => {
setIsLoading(false);
setIsResendingVerificationEmail(false);
}, 2000);
};
// Step 1 of the sign up process (enter the email or choose google authentication) // Step 1 of the sign up process (enter the email or choose google authentication)
const step1 = ( const step1 = (
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 mb-48 md:mb-16 rounded-xl drop-shadow-xl"> <div>
<p className="text-4xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary"> <div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-8 md:px-6 mx-1 rounded-xl drop-shadow-xl">
{"Let'"}s get started <p className="text-4xl font-semibold flex justify-center text-primary">
</p> {'Let\''}s get started
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-h-24 max-w-md mx-auto pt-2"> </p>
<div className="flex items-center justify-center w-5/6 md:w-full m-auto md:p-2 rounded-lg max-h-24 mt-4">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
error={emailError}
errorText={emailErrorMessage}
autoComplete="username"
/>
</div>
{/* <div className='flex flex-row justify-left mt-4 max-w-md mx-auto'>
<Checkbox className="mr-4"/>
<p className='text-sm'>I do not want to receive emails about Infisical and its products.</p>
</div> */}
<div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left">
<p className="text-gray-400 mt-2 md:mx-0.5">
By creating an account, you agree to our Terms and have read and
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" type="submit" onButtonPressed={emailCheck} size="lg" />
</div>
</div>
</div>
<div className="flex flex-col items-center justify-center w-full md:pb-2 max-w-md mx-auto pt-2 mb-48 md:mb-16 mt-2">
<Link href="/login"> <Link href="/login">
<button className="w-max pb-3 hover:opacity-90 duration-200"> <button type="button" className="w-max pb-3 hover:opacity-90 duration-200">
<u className="font-normal text-md text-sky-500"> <u className="font-normal text-sm text-primary-500">
Have an account? Log in Have an account? Log in
</u> </u>
</button> </button>
</Link> </Link>
</div> </div>
<div className="flex items-center justify-center w-5/6 md:w-full m-auto md:p-2 rounded-lg max-h-24 mt-4">
<InputField
label="Email"
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
error={emailError}
errorText={emailErrorMessage}
autoComplete="username"
/>
</div>
{/* <div className='flex flex-row justify-left mt-4 max-w-md mx-auto'>
<Checkbox className="mr-4"/>
<p className='text-sm'>I do not want to receive emails about Infisical and its products.</p>
</div> */}
<div className="flex flex-col items-center justify-center w-5/6 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-4 md:mt-4 text-sm text-center md:text-left">
<p className="text-gray-400 mt-2 md:mx-0.5">
By creating an account, you agree to our Terms and have read and
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" />
</div>
</div>
</div> </div>
); );
// Step 2 of the signup process (enter the email verification code) // Step 2 of the signup process (enter the email verification code)
const step2 = ( const step2 = (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16"> <div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-gray-400"> <p className="text-l flex justify-center text-gray-400">
{"We've"} sent a verification email to{' '} {'We\'ve'} sent a verification email to{' '}
</p> </p>
<p className="text-l flex justify-center font-semibold my-2 text-gray-400"> <p className="text-l flex justify-center font-semibold my-2 text-gray-400">
{email}{' '} {email}{' '}
@@ -328,13 +343,16 @@ export default function SignUp() {
<Button text="Verify" onButtonPressed={incrementStep} size="lg" /> <Button text="Verify" onButtonPressed={incrementStep} size="lg" />
</div> </div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2"> <div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
{/* <Link href="/login"> <div className="flex flex-row items-baseline gap-1 text-sm">
<button className="w-full hover:opacity-90 duration-200"> <span className="text-gray-400">
<u className="font-normal text-sm text-sky-700"> Not seeing an email?
Not seeing an email? Resend </span>
</u> <u className={`font-normal ${isResendingVerificationEmail ? 'text-gray-400' : 'text-primary-500 hover:opacity-90 duration-200'}`}>
</button> <button disabled={isLoading} onClick={resendVerificationEmail}>
</Link> */} {isResendingVerificationEmail ? 'Resending...' : 'Resend'}
</button>
</u>
</div>
<p className="text-sm text-gray-500 pb-2"> <p className="text-sm text-gray-500 pb-2">
Make sure to check your spam inbox. Make sure to check your spam inbox.
</p> </p>
@@ -382,7 +400,7 @@ export default function SignUp() {
setPasswordErrorLength, setPasswordErrorLength,
setPasswordErrorNumber, setPasswordErrorNumber,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
currentErrorCheck: false currentErrorCheck: false,
}); });
}} }}
type="password" type="password"
@@ -497,7 +515,7 @@ export default function SignUp() {
It contains your Secret Key which we cannot access or recover for you if It contains your Secret Key which we cannot access or recover for you if
you lose it. you lose it.
</div> </div>
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-max mx-auto mt-6 py-1 md:mt-4 text-lg text-center md:text-left"> <div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
<Button <Button
text="Download PDF" text="Download PDF"
onButtonPressed={async () => { onButtonPressed={async () => {
@@ -508,9 +526,7 @@ export default function SignUp() {
setBackupKeyError, setBackupKeyError,
setBackupKeyIssued setBackupKeyIssued
}); });
const userWorkspaces = await getWorkspaces(); router.push('/dashboard/');
const userWorkspace = userWorkspaces[0]._id;
router.push('/home/' + userWorkspace);
}} }}
size="lg" size="lg"
/> />
@@ -556,7 +572,9 @@ export default function SignUp() {
/> />
</div> </div>
</Link> </Link>
{step == 1 ? step1 : step == 2 ? step2 : step == 3 ? step3 : step4} <form onSubmit={(e) => e.preventDefault()}>
{step == 1 ? step1 : step == 2 ? step2 : step == 3 ? step3 : step4}
</form>
</div> </div>
</div> </div>
); );

Some files were not shown because too many files have changed in this diff Show More