Fixed merge conflicts
@@ -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 }}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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}`)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,4 @@ router.get(
|
|||||||
keyController.getLatestKey
|
keyController.getLatestKey
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/publicKey/infisical', keyController.getPublicKeyInfisical);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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]<-----
|
||||||
@@ -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]
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
const ACTION_PUSH_TO_HEROKU = 'pushToHeroku';
|
|
||||||
|
|
||||||
export {
|
|
||||||
ACTION_PUSH_TO_HEROKU
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ...\")")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
@@ -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>
|
||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
title: 'Frequently Asked Questions'
|
|
||||||
description: 'Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g).'
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem with SMTP
|
|
||||||
|
|
||||||
If you opt for actual SMTP server (not the local MailHog), you have to have the right environment variables set.
|
|
||||||
|
|
||||||
You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems.
|
|
||||||
|
|
||||||
You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails.
|
|
||||||
|
|
||||||
If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/unable-to-send-email-in-c-sharp-less-secure-app-access-not-longer-available/72553362#72553362) should help.
|
|
||||||
|
|
||||||
## `MONGO_URL` issues
|
|
||||||
|
|
||||||
Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`.
|
|
||||||
@@ -16,59 +16,44 @@ cd infisical
|
|||||||
|
|
||||||
## Set up environment variables
|
## Set up environment variables
|
||||||
|
|
||||||
Before running the docker-compose we have to generate the .env file with the environment variables, you can create your own file or start with the
|
Start by creating a .env file at the root of the Infisical directory. It's best to start with the provided [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) template containing the necessary envars to fill out your .env file — you only have to modify the SMTP parameters.
|
||||||
`.env.example` as an example guide.
|
|
||||||
|
|
||||||
Mandatory variables in the `.env` file:
|
<Warning>
|
||||||
|
The pre-populated environment variable values in the `.env.example` file are meant to be used in development only.
|
||||||
|
You'll want to fill in your own values in production, especially concerning encryption keys, secrets, and SMTP parameters.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
1. Keys and JWT variables
|
Refer to the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) for guidance on each envar.
|
||||||
|
|
||||||

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

|
Alternatively, you can use the provided development (Mailhog) SMTP server to send and browse emails sent by the backend on http://localhost:8025; to use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` below.
|
||||||
|
</Tip>
|
||||||
These variables are used to connect the MongoDB and set the URL for the localhost.
|
|
||||||
|
|
||||||
For development, you can use `root` for the `MONGO_USERNAME` and `example` for the `MONGO_PASSWORD` as shown in the screenshot.
|
|
||||||
|
|
||||||
Take into account that if you use your own `MONGO_USERNAME` and `MONGO_PASSWORD`, you also have to change the `MONGO_URL` with the form of `MONGO_USERNAME:MONGO_PASSWORD` after the `//` part of the URL.
|
|
||||||
|
|
||||||
3. Mail SMTP service variables
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following.
|
|
||||||
|
|
||||||
For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification.
|
|
||||||
|
|
||||||
For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable.
|
|
||||||
|
|
||||||
Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
SMTP_HOST=smtp-server
|
SMTP_HOST=smtp-server
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
SMTP_NAME=<whatever you like>
|
SMTP_[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
|
|
||||||
@@ -11,6 +11,12 @@ To generate the the token, head over to your project settings as shown below.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "GitHub Actions"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Infisical can sync secrets to GitHub repo secrets only. If your repo uses environment secrets, then stay tuned with this [issue](https://github.com/Infisical/infisical/issues/54).
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Ensure you have admin privileges to the repo you want to sync secrets to.
|
||||||
|
|
||||||
|
## Navigate to your project's integrations tab
|
||||||
|
|
||||||
|

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

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

|
||||||
|
|
||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|

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

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

|
||||||
|
|
||||||
|
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>
|
||||||
@@ -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` |
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ that by adding the `--namespace <namespace-to-install-to>` to your `helm install
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
## Installs to default namespace
|
## Installs to default namespace
|
||||||
helm install infisical-helm-charts/infisical --values <path to the values.yaml you downloaded/created in step 2>
|
helm install infisical-helm-charts/infisical --generate-name --values <path to the values.yaml you downloaded/created in step 2>
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
@@ -50,5 +50,4 @@ If you have not filled out all of the required environment variables, you will s
|
|||||||
do so.
|
do so.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
#### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
|
||||||
Note: Please allow an additional time (2 minutes) for the frontend pods to be fully ready.
|
|
||||||
@@ -1,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);
|
||||||
|
|||||||
@@ -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,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,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,
|
||||||
|
|||||||
@@ -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,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,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,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,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,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,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";
|
||||||
|
|||||||
@@ -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,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,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,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,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,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,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,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";
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||