From dce5c8f6217222502174e858ac484084f94934ef Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 10 Mar 2023 23:20:08 -0800 Subject: [PATCH 01/35] add emailConfigured to status api --- backend/src/routes/status/status.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/routes/status/status.ts b/backend/src/routes/status/status.ts index 4ab82cca9..c7f7e9d04 100644 --- a/backend/src/routes/status/status.ts +++ b/backend/src/routes/status/status.ts @@ -1,4 +1,5 @@ import express, { Request, Response } from 'express'; +import { SMTP_CONFIGURED } from '../../config'; const router = express.Router(); @@ -8,6 +9,7 @@ router.get( res.status(200).json({ date: new Date(), message: 'Ok', + emailConfigured: SMTP_CONFIGURED }) } ); From 7fd06e36bca485d37db494d899bbee13e16ea2d2 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 14 Mar 2023 16:38:30 +0700 Subject: [PATCH 02/35] Complete preliminary addition of infisical-node to support service tokens --- .env.example | 3 - backend/environment.d.ts | 1 - backend/package-lock.json | 21 ++ backend/package.json | 1 + backend/src/app.ts | 144 -------- backend/src/config/index.ts | 200 ++++++------ backend/src/controllers/v1/authController.ts | 21 +- .../v1/integrationAuthController.ts | 10 +- .../controllers/v1/membershipController.ts | 8 +- .../controllers/v1/membershipOrgController.ts | 8 +- .../controllers/v1/organizationController.ts | 28 +- .../src/controllers/v1/passwordController.ts | 8 +- .../src/controllers/v1/secretController.ts | 5 +- .../controllers/v1/serviceTokenController.ts | 4 +- .../src/controllers/v1/signupController.ts | 8 +- .../src/controllers/v1/stripeController.ts | 11 +- .../controllers/v2/apiKeyDataController.ts | 8 +- backend/src/controllers/v2/authController.ts | 16 +- .../src/controllers/v2/secretController.ts | 9 +- .../src/controllers/v2/secretsController.ts | 9 +- .../v2/serviceTokenDataController.ts | 8 +- .../src/controllers/v2/signupController.ts | 6 +- .../src/controllers/v2/workspaceController.ts | 4 +- .../src/ee/controllers/v1/stripeController.ts | 13 +- backend/src/ee/services/EELicenseService.ts | 4 +- backend/src/helpers/auth.ts | 19 +- backend/src/helpers/bot.ts | 6 +- backend/src/helpers/nodemailer.ts | 6 +- backend/src/helpers/organization.ts | 35 +- backend/src/helpers/token.ts | 6 +- backend/src/index.ts | 168 +++++++++- backend/src/integrations/exchange.ts | 49 +-- backend/src/integrations/refresh.ts | 30 +- backend/src/middleware/requestErrorHandler.ts | 9 +- backend/src/middleware/requireMfaAuth.ts | 4 +- .../src/middleware/requireServiceTokenAuth.ts | 4 +- backend/src/middleware/requireSignupAuth.ts | 4 +- backend/src/models/token.ts | 1 - backend/src/services/PostHogClient.ts | 54 +-- backend/src/services/index.ts | 5 +- backend/src/services/smtp.ts | 92 +++--- backend/src/utils/addDevelopmentUser.ts | 4 +- backend/src/utils/logger.ts | 12 +- backend/src/utils/requestError.ts | 3 +- backend/src/variables/index.ts | 4 +- backend/src/variables/integration.ts | 308 +++++++++--------- docs/self-hosting/configuration/envars.mdx | 1 - 47 files changed, 691 insertions(+), 691 deletions(-) delete mode 100644 backend/src/app.ts diff --git a/.env.example b/.env.example index c893713f4..79fca5686 100644 --- a/.env.example +++ b/.env.example @@ -16,9 +16,6 @@ JWT_AUTH_LIFETIME= JWT_REFRESH_LIFETIME= JWT_SIGNUP_LIFETIME= -# Optional lifetimes for OTP expressed in seconds -EMAIL_TOKEN_LIFETIME= - # MongoDB # Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref # to the MongoDB container instance or Mongo Cloud diff --git a/backend/environment.d.ts b/backend/environment.d.ts index 497902def..3793552b1 100644 --- a/backend/environment.d.ts +++ b/backend/environment.d.ts @@ -4,7 +4,6 @@ declare global { namespace NodeJS { interface ProcessEnv { PORT: string; - EMAIL_TOKEN_LIFETIME: string; ENCRYPTION_KEY: string; SALT_ROUNDS: string; JWT_AUTH_LIFETIME: string; diff --git a/backend/package-lock.json b/backend/package-lock.json index cef782117..5dbf691ca 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -32,6 +32,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", + "infisical-node": "^1.0.34", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", @@ -5973,6 +5974,16 @@ "node": ">=0.8.19" } }, + "node_modules/infisical-node": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.34.tgz", + "integrity": "sha512-0joSgkNPZ15aZtm8Mrr/vSWizTYZlJivbawCecfllR4bzQ03TT3Ja4hivYyAmRYkrhUTHqb0gpQ3a8lSk7vyug==", + "dependencies": { + "axios": "^1.3.3", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -16758,6 +16769,16 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "infisical-node": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.34.tgz", + "integrity": "sha512-0joSgkNPZ15aZtm8Mrr/vSWizTYZlJivbawCecfllR4bzQ03TT3Ja4hivYyAmRYkrhUTHqb0gpQ3a8lSk7vyug==", + "requires": { + "axios": "^1.3.3", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", diff --git a/backend/package.json b/backend/package.json index 90db51ece..04a5c0943 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,6 +23,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", + "infisical-node": "^1.0.34", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", diff --git a/backend/src/app.ts b/backend/src/app.ts deleted file mode 100644 index 550cf08f6..000000000 --- a/backend/src/app.ts +++ /dev/null @@ -1,144 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { patchRouterParam } = require('./utils/patchAsyncRoutes'); - -import express from 'express'; -import helmet from 'helmet'; -import cors from 'cors'; -import cookieParser from 'cookie-parser'; -import dotenv from 'dotenv'; -import swaggerUi = require('swagger-ui-express'); -// eslint-disable-next-line @typescript-eslint/no-var-requires -const swaggerFile = require('../spec.json'); -// eslint-disable-next-line @typescript-eslint/no-var-requires -const requestIp = require('request-ip'); - -dotenv.config(); -import { PORT, NODE_ENV, SITE_URL } from './config'; -import { apiLimiter } from './helpers/rateLimiter'; - -import { - workspace as eeWorkspaceRouter, - secret as eeSecretRouter, - secretSnapshot as eeSecretSnapshotRouter, - action as eeActionRouter -} from './ee/routes/v1'; -import { - signup as v1SignupRouter, - auth as v1AuthRouter, - bot as v1BotRouter, - organization as v1OrganizationRouter, - workspace as v1WorkspaceRouter, - membershipOrg as v1MembershipOrgRouter, - membership as v1MembershipRouter, - key as v1KeyRouter, - inviteOrg as v1InviteOrgRouter, - user as v1UserRouter, - userAction as v1UserActionRouter, - secret as v1SecretRouter, - serviceToken as v1ServiceTokenRouter, - password as v1PasswordRouter, - stripe as v1StripeRouter, - integration as v1IntegrationRouter, - integrationAuth as v1IntegrationAuthRouter -} from './routes/v1'; -import { - signup as v2SignupRouter, - auth as v2AuthRouter, - users as v2UsersRouter, - organizations as v2OrganizationsRouter, - workspace as v2WorkspaceRouter, - secret as v2SecretRouter, // begin to phase out - secrets as v2SecretsRouter, - serviceTokenData as v2ServiceTokenDataRouter, - apiKeyData as v2APIKeyDataRouter, - environment as v2EnvironmentRouter, - tags as v2TagsRouter, -} from './routes/v2'; - -import { healthCheck } from './routes/status'; - -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(); - -app.enable('trust proxy'); -app.use(express.json()); -app.use(cookieParser()); -app.use( - cors({ - credentials: true, - origin: SITE_URL - }) -); - -app.use(requestIp.mw()) - -if (NODE_ENV === 'production') { - // enable app-wide rate-limiting + helmet security - // in production - app.disable('x-powered-by'); - app.use(apiLimiter); - app.use(helmet()); -} - -// (EE) routes -app.use('/api/v1/secret', eeSecretRouter); -app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter); -app.use('/api/v1/workspace', eeWorkspaceRouter); -app.use('/api/v1/action', eeActionRouter); - -// v1 routes -app.use('/api/v1/signup', v1SignupRouter); -app.use('/api/v1/auth', v1AuthRouter); -app.use('/api/v1/bot', v1BotRouter); -app.use('/api/v1/user', v1UserRouter); -app.use('/api/v1/user-action', v1UserActionRouter); -app.use('/api/v1/organization', v1OrganizationRouter); -app.use('/api/v1/workspace', v1WorkspaceRouter); -app.use('/api/v1/membership-org', v1MembershipOrgRouter); -app.use('/api/v1/membership', v1MembershipRouter); -app.use('/api/v1/key', v1KeyRouter); -app.use('/api/v1/invite-org', v1InviteOrgRouter); -app.use('/api/v1/secret', v1SecretRouter); -app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated -app.use('/api/v1/password', v1PasswordRouter); -app.use('/api/v1/stripe', v1StripeRouter); -app.use('/api/v1/integration', v1IntegrationRouter); -app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); - -// v2 routes -app.use('/api/v2/signup', v2SignupRouter); -app.use('/api/v2/auth', v2AuthRouter); -app.use('/api/v2/users', v2UsersRouter); -app.use('/api/v2/organizations', v2OrganizationsRouter); -app.use('/api/v2/workspace', v2EnvironmentRouter); -app.use('/api/v2/workspace', v2TagsRouter); -app.use('/api/v2/workspace', v2WorkspaceRouter); -app.use('/api/v2/secret', v2SecretRouter); // deprecated -app.use('/api/v2/secrets', v2SecretsRouter); -app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route -app.use('/api/v2/api-key', v2APIKeyDataRouter); - -// api docs -app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)) - -// Server status -app.use('/api', healthCheck) - -//* 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, () => { - getLogger("backend-main").info(`Server started listening at port ${PORT}`) -}); diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index e3c242130..06ad3a631 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,105 +1,101 @@ -const PORT = process.env.PORT || 4000; -const EMAIL_TOKEN_LIFETIME = parseInt(process.env.EMAIL_TOKEN_LIFETIME! || '86400'); -const INVITE_ONLY_SIGNUP = process.env.INVITE_ONLY_SIGNUP == undefined ? false : process.env.INVITE_ONLY_SIGNUP -const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; -const SALT_ROUNDS = parseInt(process.env.SALT_ROUNDS!) || 10; -const JWT_AUTH_LIFETIME = process.env.JWT_AUTH_LIFETIME! || '10d'; -const JWT_AUTH_SECRET = process.env.JWT_AUTH_SECRET!; -const JWT_MFA_LIFETIME = process.env.JWT_MFA_LIFETIME! || '5m'; -const JWT_MFA_SECRET = process.env.JWT_MFA_SECRET!; -const JWT_REFRESH_LIFETIME = process.env.JWT_REFRESH_LIFETIME! || '90d'; -const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!; -const JWT_SERVICE_SECRET = process.env.JWT_SERVICE_SECRET!; -const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m'; -const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; -const MONGO_URL = process.env.MONGO_URL!; -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_ID_AZURE = process.env.CLIENT_ID_AZURE!; -const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!; -const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; -const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!; -const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!; -const CLIENT_ID_GITLAB = process.env.CLIENT_ID_GITLAB!; -const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!; -const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!; -const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!; -const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!; -const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!; -const CLIENT_SECRET_GITLAB = process.env.CLIENT_SECRET_GITLAB; -const CLIENT_SLUG_VERCEL = process.env.CLIENT_SLUG_VERCEL!; -const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com'; -const POSTHOG_PROJECT_API_KEY = - process.env.POSTHOG_PROJECT_API_KEY! || - 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; -const SENTRY_DSN = process.env.SENTRY_DSN!; -const SITE_URL = process.env.SITE_URL!; -const SMTP_HOST = process.env.SMTP_HOST!; -const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false; -const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587; -const SMTP_USERNAME = process.env.SMTP_USERNAME!; -const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; -const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!; -const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical'; -const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!; -const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!; -const STRIPE_PRODUCT_TEAM = process.env.STRIPE_PRODUCT_TEAM!; -const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!; -const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!; -const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!; -const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true; -const LICENSE_KEY = process.env.LICENSE_KEY!; +// const PORT = process.env.PORT || 4000; +// const INVITE_ONLY_SIGNUP = process.env.INVITE_ONLY_SIGNUP == undefined ? false : process.env.INVITE_ONLY_SIGNUP +// const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; +// const SALT_ROUNDS = parseInt(process.env.SALT_ROUNDS!) || 10; +// const JWT_AUTH_LIFETIME = process.env.JWT_AUTH_LIFETIME! || '10d'; +// const JWT_AUTH_SECRET = process.env.JWT_AUTH_SECRET!; +// const JWT_MFA_LIFETIME = process.env.JWT_MFA_LIFETIME! || '5m'; +// const JWT_MFA_SECRET = process.env.JWT_MFA_SECRET!; +// const JWT_REFRESH_LIFETIME = process.env.JWT_REFRESH_LIFETIME! || '90d'; +// const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!; +// const JWT_SERVICE_SECRET = process.env.JWT_SERVICE_SECRET!; +// const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m'; +// const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; +// const MONGO_URL = process.env.MONGO_URL!; +// 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_ID_AZURE = process.env.CLIENT_ID_AZURE!; +// const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!; +// const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; +// const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!; +// const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!; +// const CLIENT_ID_GITLAB = process.env.CLIENT_ID_GITLAB!; +// const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!; +// const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!; +// const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!; +// const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!; +// const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!; +// const CLIENT_SECRET_GITLAB = process.env.CLIENT_SECRET_GITLAB; +// const CLIENT_SLUG_VERCEL = process.env.CLIENT_SLUG_VERCEL!; +// const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com'; +// const POSTHOG_PROJECT_API_KEY = + // process.env.POSTHOG_PROJECT_API_KEY! || + // 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; +// const SENTRY_DSN = process.env.SENTRY_DSN!; +// const SITE_URL = process.env.SITE_URL!; +// const SMTP_HOST = process.env.SMTP_HOST!; +// const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false; +// const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587; +// const SMTP_USERNAME = process.env.SMTP_USERNAME!; +// const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; +// const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!; +// const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical'; +// const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!; +// const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!; +// const STRIPE_PRODUCT_TEAM = process.env.STRIPE_PRODUCT_TEAM!; +// const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!; +// const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!; +// const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!; +// const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true; export { - PORT, - EMAIL_TOKEN_LIFETIME, - INVITE_ONLY_SIGNUP, - ENCRYPTION_KEY, - SALT_ROUNDS, - JWT_AUTH_LIFETIME, - JWT_AUTH_SECRET, - JWT_MFA_LIFETIME, - JWT_MFA_SECRET, - JWT_REFRESH_LIFETIME, - JWT_REFRESH_SECRET, - JWT_SERVICE_SECRET, - JWT_SIGNUP_LIFETIME, - JWT_SIGNUP_SECRET, - MONGO_URL, - NODE_ENV, - VERBOSE_ERROR_OUTPUT, - LOKI_HOST, - CLIENT_ID_AZURE, - CLIENT_ID_HEROKU, - CLIENT_ID_VERCEL, - CLIENT_ID_NETLIFY, - CLIENT_ID_GITHUB, - CLIENT_ID_GITLAB, - CLIENT_SECRET_AZURE, - CLIENT_SECRET_HEROKU, - CLIENT_SECRET_VERCEL, - CLIENT_SECRET_NETLIFY, - CLIENT_SECRET_GITHUB, - CLIENT_SECRET_GITLAB, - CLIENT_SLUG_VERCEL, - POSTHOG_HOST, - POSTHOG_PROJECT_API_KEY, - SENTRY_DSN, - SITE_URL, - SMTP_HOST, - SMTP_PORT, - SMTP_SECURE, - SMTP_USERNAME, - SMTP_PASSWORD, - SMTP_FROM_ADDRESS, - SMTP_FROM_NAME, - STRIPE_PRODUCT_STARTER, - STRIPE_PRODUCT_TEAM, - STRIPE_PRODUCT_PRO, - STRIPE_PUBLISHABLE_KEY, - STRIPE_SECRET_KEY, - STRIPE_WEBHOOK_SECRET, - TELEMETRY_ENABLED, - LICENSE_KEY + // PORT, + // INVITE_ONLY_SIGNUP, + // ENCRYPTION_KEY, + // SALT_ROUNDS, + // JWT_AUTH_LIFETIME, + // JWT_AUTH_SECRET, + // JWT_MFA_LIFETIME, + // JWT_MFA_SECRET, + // JWT_REFRESH_LIFETIME, + // JWT_REFRESH_SECRET, + // JWT_SERVICE_SECRET, + // JWT_SIGNUP_LIFETIME, + // JWT_SIGNUP_SECRET, + // MONGO_URL, + // NODE_ENV, + // VERBOSE_ERROR_OUTPUT, + // LOKI_HOST, + // CLIENT_ID_AZURE, + // CLIENT_ID_HEROKU, + // CLIENT_ID_VERCEL, + // CLIENT_ID_NETLIFY, + // CLIENT_ID_GITHUB, + // CLIENT_ID_GITLAB, + // CLIENT_SECRET_AZURE, + // CLIENT_SECRET_HEROKU, + // CLIENT_SECRET_VERCEL, + // CLIENT_SECRET_NETLIFY, + // CLIENT_SECRET_GITHUB, + // CLIENT_SECRET_GITLAB, + // CLIENT_SLUG_VERCEL, + // POSTHOG_HOST, + // POSTHOG_PROJECT_API_KEY, + // SENTRY_DSN, + // SITE_URL, + // SMTP_HOST, + // SMTP_PORT, + // SMTP_SECURE, + // SMTP_USERNAME, + // SMTP_PASSWORD, + // SMTP_FROM_ADDRESS, + // SMTP_FROM_NAME, + // STRIPE_PRODUCT_STARTER, + // STRIPE_PRODUCT_TEAM, + // STRIPE_PRODUCT_PRO, + // STRIPE_PUBLISHABLE_KEY, + // STRIPE_SECRET_KEY, + // STRIPE_WEBHOOK_SECRET, + // TELEMETRY_ENABLED, }; diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index e329b15c7..732715967 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -1,8 +1,9 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; -import * as Sentry from '@sentry/node'; import * as bigintConversion from 'bigint-conversion'; +// eslint-disable-next-line @typescript-eslint/no-var-requires const jsrp = require('jsrp'); import { User, LoginSRPDetail } from '../../models'; import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth'; @@ -11,12 +12,6 @@ import { ACTION_LOGIN, ACTION_LOGOUT } from '../../variables'; -import { - NODE_ENV, - JWT_AUTH_LIFETIME, - JWT_AUTH_SECRET, - JWT_REFRESH_SECRET -} from '../../config'; import { BadRequestError } from '../../utils/errors'; import { EELogService } from '../../ee/services'; import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this @@ -126,7 +121,7 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: infisical.get('NODE_ENV')! === 'production' ? true : false }); const loginAction = await EELogService.createAction({ @@ -182,7 +177,7 @@ export const logout = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: infisical.get('NODE_ENV') === 'production' ? true : false }); const logoutAction = await EELogService.createAction({ @@ -237,7 +232,7 @@ export const getNewToken = async (req: Request, res: Response) => { } const decodedToken = ( - jwt.verify(refreshToken, JWT_REFRESH_SECRET) + jwt.verify(refreshToken, infisical.get('JWT_REFRESH_SECRET')!) ); const user = await User.findOne({ @@ -252,8 +247,8 @@ export const getNewToken = async (req: Request, res: Response) => { payload: { userId: decodedToken.userId }, - expiresIn: JWT_AUTH_LIFETIME, - secret: JWT_AUTH_SECRET + expiresIn: infisical.get('JWT_AUTH_LIFETIME')!, + secret: infisical.get('JWT_AUTH_SECRET')! }); return res.status(200).send({ diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index d26bab1cc..da8fc5570 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -5,7 +5,7 @@ import { IntegrationAuth, Bot } from '../../models'; -import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables'; +import { INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables'; import { IntegrationService } from '../../services'; import { getApps, @@ -39,9 +39,11 @@ export const getIntegrationAuth = async (req: Request, res: Response) => { } export const getIntegrationOptions = async (req: Request, res: Response) => { - return res.status(200).send({ - integrationOptions: INTEGRATION_OPTIONS, - }); + const INTEGRATION_OPTIONS = getIntegrationOptionsFunc(); + + return res.status(200).send({ + integrationOptions: INTEGRATION_OPTIONS, + }); }; /** diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index 3627ce3fb..11fc465c2 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -1,12 +1,12 @@ -import { Request, Response } from 'express'; +import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; -import { Membership, MembershipOrg, User, Key, IMembership, Workspace } from '../../models'; +import { Request, Response } from 'express'; +import { Membership, MembershipOrg, User, Key } from '../../models'; import { findMembership, deleteMembership as deleteMember } from '../../helpers/membership'; import { sendMail } from '../../helpers/nodemailer'; -import { SITE_URL } from '../../config'; import { ADMIN, MEMBER, ACCEPTED } from '../../variables'; /** @@ -215,7 +215,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => { inviterFirstName: req.user.firstName, inviterEmail: req.user.email, workspaceName: req.membership.workspace.name, - callback_url: SITE_URL + '/login' + callback_url: infisical.get('SITE_URL')! + '/login' } }); } catch (err) { diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index aa3022eac..0efe00dac 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -1,6 +1,6 @@ +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; -import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config'; import { MembershipOrg, Organization, User } from '../../models'; import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg'; import { createToken } from '../../helpers/auth'; @@ -178,7 +178,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { organizationName: organization.name, email: inviteeEmail, token, - callback_url: SITE_URL + '/signupinvite' + callback_url: infisical.get('SITE_URL') + '/signupinvite' } }); } @@ -250,8 +250,8 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: JWT_SIGNUP_LIFETIME, - secret: JWT_SIGNUP_SECRET + expiresIn: infisical.get('JWT_SIGNUP_LIFETIME')!, + secret: infisical.get('JWT_SIGNUP_SECRET')! }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/organizationController.ts b/backend/src/controllers/v1/organizationController.ts index 66326e560..44403db86 100644 --- a/backend/src/controllers/v1/organizationController.ts +++ b/backend/src/controllers/v1/organizationController.ts @@ -1,21 +1,13 @@ -import { Request, Response } from 'express'; +import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; -import { - SITE_URL, - STRIPE_SECRET_KEY -} from '../../config'; +import { Request, Response } from 'express'; import Stripe from 'stripe'; - -const stripe = new Stripe(STRIPE_SECRET_KEY, { - apiVersion: '2022-08-01' -}); import { Membership, MembershipOrg, Organization, Workspace, - IncidentContactOrg, - IMembershipOrg + IncidentContactOrg } from '../../models'; import { createOrganization as create } from '../../helpers/organization'; import { addMembershipsOrg } from '../../helpers/membershipOrg'; @@ -325,6 +317,10 @@ export const createOrganizationPortalSession = async ( ) => { let session; try { + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); + // check if there is a payment method on file const paymentMethods = await stripe.paymentMethods.list({ customer: req.membershipOrg.organization.customerId, @@ -337,13 +333,13 @@ export const createOrganizationPortalSession = async ( customer: req.membershipOrg.organization.customerId, mode: 'setup', payment_method_types: ['card'], - success_url: SITE_URL + '/dashboard', - cancel_url: SITE_URL + '/dashboard' + success_url: infisical.get('SITE_URL')! + '/dashboard', + cancel_url: infisical.get('SITE_URL')! + '/dashboard' }); } else { session = await stripe.billingPortal.sessions.create({ customer: req.membershipOrg.organization.customerId, - return_url: SITE_URL + '/dashboard' + return_url: infisical.get('SITE_URL') + '/dashboard' }); } @@ -369,6 +365,10 @@ export const getOrganizationSubscriptions = async ( ) => { let subscriptions; try { + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); + subscriptions = await stripe.subscriptions.list({ customer: req.membershipOrg.organization.customerId }); diff --git a/backend/src/controllers/v1/passwordController.ts b/backend/src/controllers/v1/passwordController.ts index 63d2b9184..72a649bb1 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -1,3 +1,4 @@ +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -7,7 +8,6 @@ import { User, BackupPrivateKey, LoginSRPDetail } from '../../models'; import { createToken } from '../../helpers/auth'; import { sendMail } from '../../helpers/nodemailer'; import { TokenService } from '../../services'; -import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config'; import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables'; import { BadRequestError } from '../../utils/errors'; @@ -44,7 +44,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => { substitutions: { email, token, - callback_url: SITE_URL + '/password-reset' + callback_url: infisical.get('SITE_URL')! + '/password-reset' } }); } catch (err) { @@ -91,8 +91,8 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: JWT_SIGNUP_LIFETIME, - secret: JWT_SIGNUP_SECRET + expiresIn: infisical.get('JWT_SIGNUP_LIFETIME')!, + secret: infisical.get('JWT_SIGNUP_SECRET')! }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/secretController.ts b/backend/src/controllers/v1/secretController.ts index c76e5e883..1d1f8981c 100644 --- a/backend/src/controllers/v1/secretController.ts +++ b/backend/src/controllers/v1/secretController.ts @@ -9,7 +9,7 @@ import { import { pushKeys } from '../../helpers/key'; import { eventPushSecrets } from '../../events'; import { EventService } from '../../services'; -import { postHogClient } from '../../services'; +import { getPostHogClient } from '../../services'; interface PushSecret { ciphertextKey: string; @@ -38,6 +38,7 @@ export const pushSecrets = async (req: Request, res: Response) => { // upload (encrypted) secrets to workspace with id [workspaceId] try { + const postHogClient = getPostHogClient(); let { secrets }: { secrets: PushSecret[] } = req.body; const { keys, environment, channel } = req.body; const { workspaceId } = req.params; @@ -111,6 +112,7 @@ export const pullSecrets = async (req: Request, res: Response) => { let secrets; let key; try { + const postHogClient = getPostHogClient(); const environment: string = req.query.environment as string; const channel: string = req.query.channel as string; const { workspaceId } = req.params; @@ -179,6 +181,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => { let secrets; let key; try { + const postHogClient = getPostHogClient(); const environment: string = req.query.environment as string; const channel: string = req.query.channel as string; const { workspaceId } = req.params; diff --git a/backend/src/controllers/v1/serviceTokenController.ts b/backend/src/controllers/v1/serviceTokenController.ts index 3fafb9043..76899a1a7 100644 --- a/backend/src/controllers/v1/serviceTokenController.ts +++ b/backend/src/controllers/v1/serviceTokenController.ts @@ -1,7 +1,7 @@ +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import { ServiceToken } from '../../models'; import { createToken } from '../../helpers/auth'; -import { JWT_SERVICE_SECRET } from '../../config'; /** * Return service token on request @@ -61,7 +61,7 @@ export const createServiceToken = async (req: Request, res: Response) => { workspaceId }, expiresIn: expiresIn, - secret: JWT_SERVICE_SECRET + secret: infisical.get('JWT_SERVICE_SECRET')! }); } catch (err) { return res.status(400).send({ diff --git a/backend/src/controllers/v1/signupController.ts b/backend/src/controllers/v1/signupController.ts index 1c5c9e298..dbf5bd5a4 100644 --- a/backend/src/controllers/v1/signupController.ts +++ b/backend/src/controllers/v1/signupController.ts @@ -1,7 +1,7 @@ +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import { User } from '../../models'; -import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, INVITE_ONLY_SIGNUP } from '../../config'; import { sendEmailVerification, checkEmailVerification, @@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => { try { email = req.body.email; - if (INVITE_ONLY_SIGNUP) { + if (infisical.get('INVITE_ONLY_SIGNUP') || false) { // Only one user can create an account without being invited. The rest need to be invited in order to make an account const userCount = await User.countDocuments({}) if (userCount != 0) { @@ -91,8 +91,8 @@ export const verifyEmailSignup = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: JWT_SIGNUP_LIFETIME, - secret: JWT_SIGNUP_SECRET + expiresIn: infisical.get('JWT_SIGNUP_LIFETIME')!, + secret: infisical.get('JWT_SIGNUP_SECRET')! }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/stripeController.ts b/backend/src/controllers/v1/stripeController.ts index 15f62a056..1d3ee0bf2 100644 --- a/backend/src/controllers/v1/stripeController.ts +++ b/backend/src/controllers/v1/stripeController.ts @@ -1,10 +1,7 @@ +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import Stripe from 'stripe'; -import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../../config'; -const stripe = new Stripe(STRIPE_SECRET_KEY, { - apiVersion: '2022-08-01' -}); /** * Handle service provisioning/un-provisioning via Stripe @@ -16,11 +13,15 @@ export const handleWebhook = async (req: Request, res: Response) => { let event; try { // check request for valid stripe signature + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); + const sig = req.headers['stripe-signature'] as string; event = stripe.webhooks.constructEvent( req.body, sig, - STRIPE_WEBHOOK_SECRET // ? + infisical.get('STRIPE_WEBHOOK_SECRET')! ); } catch (err) { Sentry.setUser({ email: req.user.email }); diff --git a/backend/src/controllers/v2/apiKeyDataController.ts b/backend/src/controllers/v2/apiKeyDataController.ts index cafbacb5b..b6819928a 100644 --- a/backend/src/controllers/v2/apiKeyDataController.ts +++ b/backend/src/controllers/v2/apiKeyDataController.ts @@ -1,13 +1,11 @@ -import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; +import { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; import { APIKeyData } from '../../models'; -import { - SALT_ROUNDS -} from '../../config'; /** * Return API key data for user with id [req.user_id] @@ -45,7 +43,7 @@ export const createAPIKeyData = async (req: Request, res: Response) => { const { name, expiresIn } = req.body; const secret = crypto.randomBytes(16).toString('hex'); - const secretHash = await bcrypt.hash(secret, SALT_ROUNDS); + const secretHash = await bcrypt.hash(secret, parseInt(infisical.get('SALT_ROUNDS')!) || 10); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); diff --git a/backend/src/controllers/v2/authController.ts b/backend/src/controllers/v2/authController.ts index 95a1613d2..4401e2498 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -1,3 +1,4 @@ +import infisical from 'infisical-node'; /* eslint-disable @typescript-eslint/no-var-requires */ import { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; @@ -10,11 +11,6 @@ import { checkUserDevice } from '../../helpers/user'; import { sendMail } from '../../helpers/nodemailer'; import { TokenService } from '../../services'; import { EELogService } from '../../ee/services'; -import { - NODE_ENV, - JWT_MFA_LIFETIME, - JWT_MFA_SECRET -} from '../../config'; import { BadRequestError, InternalServerError } from '../../utils/errors'; import { TOKEN_EMAIL_MFA, @@ -28,8 +24,6 @@ declare module 'jsonwebtoken' { } } -const clientPublicKeys: any = {}; - /** * Log in user step 1: Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol * @param req @@ -126,8 +120,8 @@ export const login2 = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: JWT_MFA_LIFETIME, - secret: JWT_MFA_SECRET + expiresIn: infisical.get('JWT_MFA_LIFETIME')!, + secret: infisical.get('JWT_MFA_SECRET')! }); const code = await TokenService.createToken({ @@ -165,7 +159,7 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: infisical.get('NODE_ENV')! === 'production' ? true : false }); // case: user does not have MFA enablgged @@ -304,7 +298,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: infisical.get('NODE_ENV')! === 'production' ? true : false }); interface VerifyMfaTokenRes { diff --git a/backend/src/controllers/v2/secretController.ts b/backend/src/controllers/v2/secretController.ts index 89567e616..cd91dca67 100644 --- a/backend/src/controllers/v2/secretController.ts +++ b/backend/src/controllers/v2/secretController.ts @@ -7,7 +7,7 @@ const { ValidationError } = mongoose.Error; import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors'; import { AnyBulkWriteOperation } from 'mongodb'; import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables"; -import { postHogClient } from '../../services'; +import { getPostHogClient } from '../../services'; /** * Create secret for workspace with id [workspaceId] and environment [environment] @@ -15,6 +15,7 @@ import { postHogClient } from '../../services'; * @param res */ export const createSecret = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); const secretToCreate: CreateSecretRequestBody = req.body.secret; const { workspaceId, environment } = req.params const sanitizedSecret: SanitizedSecretForCreate = { @@ -67,6 +68,7 @@ export const createSecret = async (req: Request, res: Response) => { * @param res */ export const createSecrets = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets; const { workspaceId, environment } = req.params const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [] @@ -128,6 +130,7 @@ export const createSecrets = async (req: Request, res: Response) => { * @param res */ export const deleteSecrets = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); const { workspaceId, environmentName } = req.params const secretIdsToDelete: string[] = req.body.secretIds @@ -181,6 +184,7 @@ export const deleteSecrets = async (req: Request, res: Response) => { * @param res */ export const deleteSecret = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); await Secret.findByIdAndDelete(req._secret._id) if (postHogClient) { @@ -209,6 +213,7 @@ export const deleteSecret = async (req: Request, res: Response) => { * @returns */ export const updateSecrets = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); const { workspaceId, environmentName } = req.params const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets; const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) @@ -276,6 +281,7 @@ export const updateSecrets = async (req: Request, res: Response) => { * @returns */ export const updateSecret = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); const { workspaceId, environmentName } = req.params const secretModificationsRequested: ModifySecretRequestBody = req.body.secret; @@ -329,6 +335,7 @@ export const updateSecret = async (req: Request, res: Response) => { * @returns */ export const getSecrets = async (req: Request, res: Response) => { + const postHogClient = getPostHogClient(); const { environment } = req.query; const { workspaceId } = req.params; diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 486bf017e..66aecd423 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -15,7 +15,7 @@ import { UnauthorizedRequestError, ValidationError } from '../../utils/errors'; import { EventService } from '../../services'; import { eventPushSecrets } from '../../events'; import { EESecretService, EELogService } from '../../ee/services'; -import { postHogClient } from '../../services'; +import { getPostHogClient } from '../../services'; import { getChannelFromUserAgent } from '../../utils/posthog'; import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization'; import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions'; @@ -33,6 +33,8 @@ import { */ export const batchSecrets = async (req: Request, res: Response) => { const channel = getChannelFromUserAgent(req.headers['user-agent']); + const postHogClient = getPostHogClient(); + const { workspaceId, environment, @@ -326,6 +328,7 @@ export const createSecrets = async (req: Request, res: Response) => { } } */ + const postHogClient = getPostHogClient(); const channel = getChannelFromUserAgent(req.headers['user-agent']) const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body; @@ -530,6 +533,7 @@ export const getSecrets = async (req: Request, res: Response) => { } */ + const postHogClient = getPostHogClient(); const { workspaceId, environment, tagSlugs } = req.query; const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : []; @@ -732,6 +736,7 @@ export const updateSecrets = async (req: Request, res: Response) => { } } */ + const postHogClient = getPostHogClient(); const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli'; // TODO: move type @@ -953,7 +958,7 @@ export const deleteSecrets = async (req: Request, res: Response) => { } } */ - + const postHogClient = getPostHogClient(); const channel = getChannelFromUserAgent(req.headers['user-agent']) const toDelete = req.secrets.map((s: any) => s._id); diff --git a/backend/src/controllers/v2/serviceTokenDataController.ts b/backend/src/controllers/v2/serviceTokenDataController.ts index cabedabea..295664cf9 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -1,13 +1,11 @@ -import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; +import { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; import { ServiceTokenData } from '../../models'; -import { - SALT_ROUNDS -} from '../../config'; import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions'; import { ABILITY_READ } from '../../variables/organization'; @@ -75,7 +73,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => { } const secret = crypto.randomBytes(16).toString('hex'); - const secretHash = await bcrypt.hash(secret, SALT_ROUNDS); + const secretHash = await bcrypt.hash(secret, parseInt(infisical.get('SALT_ROUNDS')!) || 10); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); diff --git a/backend/src/controllers/v2/signupController.ts b/backend/src/controllers/v2/signupController.ts index 79cb6730d..0eb148e37 100644 --- a/backend/src/controllers/v2/signupController.ts +++ b/backend/src/controllers/v2/signupController.ts @@ -1,3 +1,4 @@ +import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import { User, MembershipOrg } from '../../models'; @@ -7,7 +8,6 @@ import { } from '../../helpers/signup'; import { issueAuthTokens } from '../../helpers/auth'; import { INVITED, ACCEPTED } from '../../variables'; -import { NODE_ENV } from '../../config'; import request from '../../config/request'; /** @@ -127,7 +127,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: infisical.get('NODE_ENV')! === 'production' ? true : false }); } catch (err) { Sentry.setUser(null); @@ -232,7 +232,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: infisical.get('NODE_ENV')! === 'production' ? true : false }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 55cd02fff..650c70610 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -19,7 +19,7 @@ import { reformatPullSecrets } from '../../helpers/secret'; import { pushKeys } from '../../helpers/key'; -import { postHogClient, EventService } from '../../services'; +import { getPostHogClient, EventService } from '../../services'; import { eventPushSecrets } from '../../events'; interface V2PushSecret { @@ -48,6 +48,7 @@ interface V2PushSecret { export const pushWorkspaceSecrets = async (req: Request, res: Response) => { // upload (encrypted) secrets to workspace with id [workspaceId] try { + const postHogClient = getPostHogClient(); let { secrets }: { secrets: V2PushSecret[] } = req.body; const { keys, environment, channel } = req.body; const { workspaceId } = req.params; @@ -121,6 +122,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => { export const pullSecrets = async (req: Request, res: Response) => { let secrets; try { + const postHogClient = getPostHogClient(); const environment: string = req.query.environment as string; const channel: string = req.query.channel as string; const { workspaceId } = req.params; diff --git a/backend/src/ee/controllers/v1/stripeController.ts b/backend/src/ee/controllers/v1/stripeController.ts index faef14cea..7c816550e 100644 --- a/backend/src/ee/controllers/v1/stripeController.ts +++ b/backend/src/ee/controllers/v1/stripeController.ts @@ -1,10 +1,7 @@ -import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; +import { Request, Response } from 'express'; import Stripe from 'stripe'; -import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../../../config'; -const stripe = new Stripe(STRIPE_SECRET_KEY, { - apiVersion: '2022-08-01' -}); /** * Handle service provisioning/un-provisioning via Stripe @@ -15,12 +12,16 @@ const stripe = new Stripe(STRIPE_SECRET_KEY, { export const handleWebhook = async (req: Request, res: Response) => { let event; try { + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); + // check request for valid stripe signature const sig = req.headers['stripe-signature'] as string; event = stripe.webhooks.constructEvent( req.body, sig, - STRIPE_WEBHOOK_SECRET // ? + infisical.get('STRIPE_WEBHOOK_SECRET')! ); } catch (err) { Sentry.setUser({ email: req.user.email }); diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index f31482dde..4bd811340 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -1,5 +1,3 @@ -import { LICENSE_KEY } from '../../config'; - /** * Class to handle Enterprise Edition license actions */ @@ -16,4 +14,4 @@ class EELicenseService { } } -export default new EELicenseService(LICENSE_KEY); \ No newline at end of file +export default new EELicenseService('N/A'); \ No newline at end of file diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index 102a7ac07..680120395 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -1,5 +1,6 @@ -import jwt from 'jsonwebtoken'; import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; +import jwt from 'jsonwebtoken'; import bcrypt from 'bcrypt'; import { IUser, @@ -7,12 +8,6 @@ import { ServiceTokenData, APIKeyData } from '../models'; -import { - JWT_AUTH_LIFETIME, - JWT_AUTH_SECRET, - JWT_REFRESH_LIFETIME, - JWT_REFRESH_SECRET -} from '../config'; import { AccountNotFoundError, ServiceTokenDataNotFoundError, @@ -93,7 +88,7 @@ const getAuthUserPayload = async ({ let user; try { const decodedToken = ( - jwt.verify(authTokenValue, JWT_AUTH_SECRET) + jwt.verify(authTokenValue, infisical.get('JWT_AUTH_SECRET')!) ); user = await User.findOne({ @@ -224,16 +219,16 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => { payload: { userId }, - expiresIn: JWT_AUTH_LIFETIME, - secret: JWT_AUTH_SECRET + expiresIn: infisical.get('JWT_AUTH_LIFETIME')!, + secret: infisical.get('JWT_AUTH_SECRET')! }); refreshToken = createToken({ payload: { userId }, - expiresIn: JWT_REFRESH_LIFETIME, - secret: JWT_REFRESH_SECRET + expiresIn: infisical.get('JWT_REFRESH_LIFETIME')!, + secret: infisical.get('JWT_REFRESH_SECRET')! }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index 7519ef18b..8912befd1 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -1,4 +1,5 @@ import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; import { Bot, BotKey, @@ -12,7 +13,6 @@ import { decryptSymmetric, decryptAsymmetric } from '../utils/crypto'; -import { ENCRYPTION_KEY } from '../config'; import { SECRET_SHARED } from '../variables'; /** @@ -33,7 +33,7 @@ const createBot = async ({ const { publicKey, privateKey } = generateKeyPair(); const { ciphertext, iv, tag } = encryptSymmetric({ plaintext: privateKey, - key: ENCRYPTION_KEY + key: infisical.get('ENCRYPTION_KEY')! }); bot = await new Bot({ @@ -130,7 +130,7 @@ const getKey = async ({ workspaceId }: { workspaceId: string }) => { ciphertext: bot.encryptedPrivateKey, iv: bot.iv, tag: bot.tag, - key: ENCRYPTION_KEY + key: infisical.get('ENCRYPTION_KEY')! }); key = decryptAsymmetric({ diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 958342aae..45fce5d1d 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -1,9 +1,9 @@ +import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; import nodemailer from 'nodemailer'; -import { SMTP_FROM_NAME, SMTP_FROM_ADDRESS } from '../config'; -import * as Sentry from '@sentry/node'; let smtpTransporter: nodemailer.Transporter; @@ -34,7 +34,7 @@ const sendMail = async ({ const htmlToSend = temp(substitutions); await smtpTransporter.sendMail({ - from: `"${SMTP_FROM_NAME}" <${SMTP_FROM_ADDRESS}>`, + from: `"${infisical.get('SMTP_FROM_NAME')!}" <${infisical.get('SMTP_FROM_ADDRESS')!}>`, to: recipients.join(', '), subject: subjectLine, html: htmlToSend diff --git a/backend/src/helpers/organization.ts b/backend/src/helpers/organization.ts index 4ba3592de..87711da92 100644 --- a/backend/src/helpers/organization.ts +++ b/backend/src/helpers/organization.ts @@ -1,24 +1,10 @@ +import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import Stripe from 'stripe'; -import { - STRIPE_SECRET_KEY, - STRIPE_PRODUCT_STARTER, - STRIPE_PRODUCT_TEAM, - STRIPE_PRODUCT_PRO -} from '../config'; -const stripe = new Stripe(STRIPE_SECRET_KEY, { - apiVersion: '2022-08-01' -}); import { Types } from 'mongoose'; import { ACCEPTED } from '../variables'; import { Organization, MembershipOrg } from '../models'; -const productToPriceMap = { - starter: STRIPE_PRODUCT_STARTER, - team: STRIPE_PRODUCT_TEAM, - pro: STRIPE_PRODUCT_PRO -}; - /** * Create an organization with name [name] * @param {Object} obj @@ -36,8 +22,11 @@ const createOrganization = async ({ let organization; try { // register stripe account + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); - if (STRIPE_SECRET_KEY) { + if (infisical.get('STRIPE_SECRET_KEY')) { const customer = await stripe.customers.create({ email, description: name @@ -87,6 +76,16 @@ const initSubscriptionOrg = async ({ if (organization) { if (organization.customerId) { // initialize starter subscription with quantity of 0 + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); + + const productToPriceMap = { + starter: infisical.get('STRIPE_PRODUCT_STARTER')!, + team: infisical.get('STRIPE_PRODUCT_TEAM')!, + pro: infisical.get('STRIPE_PRODUCT_PRO')! + }; + stripeSubscription = await stripe.subscriptions.create({ customer: organization.customerId, items: [ @@ -139,6 +138,10 @@ const updateSubscriptionOrgQuantity = async ({ status: ACCEPTED }); + const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + apiVersion: '2022-08-01' + }); + const subscription = ( await stripe.subscriptions.list({ customer: organization.customerId diff --git a/backend/src/helpers/token.ts b/backend/src/helpers/token.ts index ca8838151..b2365cb2c 100644 --- a/backend/src/helpers/token.ts +++ b/backend/src/helpers/token.ts @@ -1,4 +1,5 @@ import * as Sentry from '@sentry/node'; +import infisical from 'infisical-node'; import { Types } from 'mongoose'; import { TokenData } from '../models'; import crypto from 'crypto'; @@ -9,9 +10,6 @@ import { TOKEN_EMAIL_ORG_INVITATION, TOKEN_EMAIL_PASSWORD_RESET } from '../variables'; -import { - SALT_ROUNDS -} from '../config'; import { UnauthorizedRequestError } from '../utils/errors'; /** @@ -86,7 +84,7 @@ const createTokenHelper = async ({ const query: TokenDataQuery = { type }; const update: TokenDataUpdate = { type, - tokenHash: await bcrypt.hash(token, SALT_ROUNDS), + tokenHash: await bcrypt.hash(token, parseInt(infisical.get('SALT_ROUNDS')!) || 10), expiresAt } diff --git a/backend/src/index.ts b/backend/src/index.ts index a97b5b33b..24172fc89 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,28 +1,168 @@ import dotenv from 'dotenv'; dotenv.config(); - +import infisical from 'infisical-node'; +import express from 'express'; +import helmet from 'helmet'; +import cors from 'cors'; import * as Sentry from '@sentry/node'; -import { SENTRY_DSN, NODE_ENV, MONGO_URL } from './config'; -import { server } from './app'; import { DatabaseService } from './services'; import { setUpHealthEndpoint } from './services/health'; import { initSmtp } from './services/smtp'; +import { logTelemetryMessage } from './services'; import { setTransporter } from './helpers/nodemailer'; import { createTestUserForDevelopment } from './utils/addDevelopmentUser'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { patchRouterParam } = require('./utils/patchAsyncRoutes'); -DatabaseService.initDatabase(MONGO_URL); +import cookieParser from 'cookie-parser'; +import swaggerUi = require('swagger-ui-express'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const swaggerFile = require('../spec.json'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const requestIp = require('request-ip'); +import { apiLimiter } from './helpers/rateLimiter'; +import { + workspace as eeWorkspaceRouter, + secret as eeSecretRouter, + secretSnapshot as eeSecretSnapshotRouter, + action as eeActionRouter +} from './ee/routes/v1'; +import { + signup as v1SignupRouter, + auth as v1AuthRouter, + bot as v1BotRouter, + organization as v1OrganizationRouter, + workspace as v1WorkspaceRouter, + membershipOrg as v1MembershipOrgRouter, + membership as v1MembershipRouter, + key as v1KeyRouter, + inviteOrg as v1InviteOrgRouter, + user as v1UserRouter, + userAction as v1UserActionRouter, + secret as v1SecretRouter, + serviceToken as v1ServiceTokenRouter, + password as v1PasswordRouter, + stripe as v1StripeRouter, + integration as v1IntegrationRouter, + integrationAuth as v1IntegrationAuthRouter +} from './routes/v1'; +import { + signup as v2SignupRouter, + auth as v2AuthRouter, + users as v2UsersRouter, + organizations as v2OrganizationsRouter, + workspace as v2WorkspaceRouter, + secret as v2SecretRouter, // begin to phase out + secrets as v2SecretsRouter, + serviceTokenData as v2ServiceTokenDataRouter, + apiKeyData as v2APIKeyDataRouter, + environment as v2EnvironmentRouter, + tags as v2TagsRouter, +} from './routes/v2'; +import { healthCheck } from './routes/status'; +import { getLogger } from './utils/logger'; +import { RouteNotFoundError } from './utils/errors'; +import { requestErrorHandler } from './middleware/requestErrorHandler'; -setUpHealthEndpoint(server); +const main = async () => { + const client = await infisical.connect({ + token: process.env.INFISICAL_TOKEN!, + debug: true + }); + + logTelemetryMessage(); + setTransporter(initSmtp()); -setTransporter(initSmtp()); + await DatabaseService.initDatabase(infisical.get('MONGO_URL')!); + if (infisical.get('NODE_ENV') !== 'test') { + Sentry.init({ + dsn: infisical.get('SENTRY_DSN') as string, + tracesSampleRate: 1.0, + debug: infisical.get('NODE_ENV') === 'production' ? false : true, + environment: infisical.get('NODE_ENV') as string + }); + } -if (NODE_ENV !== 'test') { - Sentry.init({ - dsn: SENTRY_DSN, - tracesSampleRate: 1.0, - debug: NODE_ENV === 'production' ? false : true, - environment: NODE_ENV - }); + patchRouterParam(); + const app = express(); + app.enable('trust proxy'); + app.use(express.json()); + app.use(cookieParser()); + app.use( + cors({ + credentials: true, + origin: infisical.get('SITE_URL') as string + }) + ); + + app.use(requestIp.mw()); + + if (infisical.get('NODE_ENV') === 'production') { + // enable app-wide rate-limiting + helmet security + // in production + app.disable('x-powered-by'); + app.use(apiLimiter); + app.use(helmet()); + } + + // (EE) routes + app.use('/api/v1/secret', eeSecretRouter); + app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter); + app.use('/api/v1/workspace', eeWorkspaceRouter); + app.use('/api/v1/action', eeActionRouter); + + // v1 routes + app.use('/api/v1/signup', v1SignupRouter); + app.use('/api/v1/auth', v1AuthRouter); + app.use('/api/v1/bot', v1BotRouter); + app.use('/api/v1/user', v1UserRouter); + app.use('/api/v1/user-action', v1UserActionRouter); + app.use('/api/v1/organization', v1OrganizationRouter); + app.use('/api/v1/workspace', v1WorkspaceRouter); + app.use('/api/v1/membership-org', v1MembershipOrgRouter); + app.use('/api/v1/membership', v1MembershipRouter); + app.use('/api/v1/key', v1KeyRouter); + app.use('/api/v1/invite-org', v1InviteOrgRouter); + app.use('/api/v1/secret', v1SecretRouter); + app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated + app.use('/api/v1/password', v1PasswordRouter); + app.use('/api/v1/stripe', v1StripeRouter); + app.use('/api/v1/integration', v1IntegrationRouter); + app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); + + // v2 routes + app.use('/api/v2/signup', v2SignupRouter); + app.use('/api/v2/auth', v2AuthRouter); + app.use('/api/v2/users', v2UsersRouter); + app.use('/api/v2/organizations', v2OrganizationsRouter); + app.use('/api/v2/workspace', v2EnvironmentRouter); + app.use('/api/v2/workspace', v2TagsRouter); + app.use('/api/v2/workspace', v2WorkspaceRouter); + app.use('/api/v2/secret', v2SecretRouter); // deprecated + app.use('/api/v2/secrets', v2SecretsRouter); + app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route + app.use('/api/v2/api-key', v2APIKeyDataRouter); + + // api docs + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)) + + // Server status + app.use('/api', healthCheck) + + //* 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` })) + }) + + app.use(requestErrorHandler) + + const server = app.listen(Number(infisical.get('PORT')) || 4000, () => { + createTestUserForDevelopment(); + getLogger("backend-main").info(`Server started listening at port ${Number(infisical.get('PORT')) || 4000}`) + }); + + setUpHealthEndpoint(server); } -createTestUserForDevelopment() +main(); \ No newline at end of file diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 224979c88..4e89e3aa9 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -1,5 +1,6 @@ -import request from '../config/request'; +import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; +import request from '../config/request'; import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -14,20 +15,6 @@ import { INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL } from '../variables'; -import { - SITE_URL, - CLIENT_ID_AZURE, - CLIENT_ID_VERCEL, - CLIENT_ID_NETLIFY, - CLIENT_ID_GITHUB, - CLIENT_ID_GITLAB, - CLIENT_SECRET_AZURE, - CLIENT_SECRET_HEROKU, - CLIENT_SECRET_VERCEL, - CLIENT_SECRET_NETLIFY, - CLIENT_SECRET_GITHUB, - CLIENT_SECRET_GITLAB, -} from '../config'; interface ExchangeCodeAzureResponse { token_type: string; @@ -159,9 +146,9 @@ const exchangeCodeAzure = async ({ grant_type: 'authorization_code', code: code, scope: 'https://vault.azure.net/.default openid offline_access', - client_id: CLIENT_ID_AZURE, - client_secret: CLIENT_SECRET_AZURE, - redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback` + client_id: infisical.get('CLIENT_ID_AZURE')!, + client_secret: infisical.get('CLIENT_SECRET_AZURE')!, + redirect_uri: `${infisical.get('SITE_URL')!}/integrations/azure-key-vault/oauth2/callback` } as any) )).data; @@ -204,7 +191,7 @@ const exchangeCodeHeroku = async ({ new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_secret: CLIENT_SECRET_HEROKU + client_secret: infisical.get('CLIENT_SECRET_HEROKU')! } as any) )).data; @@ -242,9 +229,9 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => { INTEGRATION_VERCEL_TOKEN_URL, new URLSearchParams({ code: code, - client_id: CLIENT_ID_VERCEL, - client_secret: CLIENT_SECRET_VERCEL, - redirect_uri: `${SITE_URL}/integrations/vercel/oauth2/callback` + client_id: infisical.get('CLIENT_ID_VERCEL')!, + client_secret: infisical.get('CLIENT_SECRET_VERCEL')!, + redirect_uri: `${infisical.get('SITE_URL')!}/integrations/vercel/oauth2/callback` } as any) ) ).data; @@ -282,9 +269,9 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => { new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_id: CLIENT_ID_NETLIFY, - client_secret: CLIENT_SECRET_NETLIFY, - redirect_uri: `${SITE_URL}/integrations/netlify/oauth2/callback` + client_id: infisical.get('CLIENT_ID_NETLIFY')!, + client_secret: infisical.get('CLIENT_SECRET_NETLIFY')!, + redirect_uri: `${infisical.get('SITE_URL')!}/integrations/netlify/oauth2/callback` } as any) ) ).data; @@ -333,10 +320,10 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => { res = ( await request.get(INTEGRATION_GITHUB_TOKEN_URL, { params: { - client_id: CLIENT_ID_GITHUB, - client_secret: CLIENT_SECRET_GITHUB, + client_id: infisical.get('CLIENT_ID_GITHUB')!, + client_secret: infisical.get('CLIENT_SECRET_GITHUB')!, code: code, - redirect_uri: `${SITE_URL}/integrations/github/oauth2/callback` + redirect_uri: `${infisical.get('SITE_URL')!}/integrations/github/oauth2/callback` }, headers: { 'Accept': 'application/json', @@ -379,9 +366,9 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => { new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_id: CLIENT_ID_GITLAB, - client_secret: CLIENT_SECRET_GITLAB, - redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback` + client_id: infisical.get('CLIENT_ID_GITLAB')!, + client_secret: infisical.get('CLIENT_SECRET_GITLAB')!, + redirect_uri: `${infisical.get('SITE_URL')}/integrations/gitlab/oauth2/callback` } as any), { headers: { diff --git a/backend/src/integrations/refresh.ts b/backend/src/integrations/refresh.ts index a3aec1ebd..67cd382c1 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -1,5 +1,6 @@ -import request from '../config/request'; +import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; +import request from '../config/request'; import { IIntegrationAuth } from '../models'; @@ -8,14 +9,13 @@ import { INTEGRATION_HEROKU, INTEGRATION_GITLAB, } from '../variables'; -import { - SITE_URL, - CLIENT_ID_AZURE, - CLIENT_ID_GITLAB, - CLIENT_SECRET_AZURE, - CLIENT_SECRET_HEROKU, - CLIENT_SECRET_GITLAB -} from '../config'; +// import { +// CLIENT_ID_AZURE, +// CLIENT_ID_GITLAB, +// CLIENT_SECRET_AZURE, +// CLIENT_SECRET_HEROKU, +// CLIENT_SECRET_GITLAB +// } from '../config'; import { INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL, @@ -133,11 +133,11 @@ const exchangeRefreshAzure = async ({ const { data }: { data: RefreshTokenAzureResponse } = await request.post( INTEGRATION_AZURE_TOKEN_URL, new URLSearchParams({ - client_id: CLIENT_ID_AZURE, + client_id: infisical.get('CLIENT_ID_AZURE')!, scope: 'openid offline_access', refresh_token: refreshToken, grant_type: 'refresh_token', - client_secret: CLIENT_SECRET_AZURE + client_secret: infisical.get('CLIENT_SECRET_AZURE')! } as any) ); @@ -180,7 +180,7 @@ const exchangeRefreshHeroku = async ({ new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, - client_secret: CLIENT_SECRET_HEROKU + client_secret: infisical.get('CLIENT_SECRET_HEROKU')! } as any) ); @@ -223,9 +223,9 @@ const exchangeRefreshGitLab = async ({ new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, - client_id: CLIENT_ID_GITLAB, - client_secret: CLIENT_SECRET_GITLAB, - redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback` + client_id: infisical.get('CLIENT_ID_GITLAB')!, + client_secret: infisical.get('CLIENT_SECRET_GITLAB')!, + redirect_uri: `${infisical.get('SITE_URL')!}/integrations/gitlab/oauth2/callback` } as any), { headers: { diff --git a/backend/src/middleware/requestErrorHandler.ts b/backend/src/middleware/requestErrorHandler.ts index 653c0df2e..2a881e814 100644 --- a/backend/src/middleware/requestErrorHandler.ts +++ b/backend/src/middleware/requestErrorHandler.ts @@ -1,16 +1,13 @@ -import { ErrorRequestHandler } from "express"; - +import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; +import { ErrorRequestHandler } from "express"; import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; import { getLogger } from "../utils/logger"; import RequestError, { LogLevel } from "../utils/requestError"; -import { NODE_ENV } from "../config"; - -import { TokenExpiredError } from 'jsonwebtoken'; export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => { if (res.headersSent) return next(); - if (NODE_ENV !== "production") { + if (infisical.get('NODE_ENV')! !== "production") { /* eslint-disable no-console */ console.log(error) /* eslint-enable no-console */ diff --git a/backend/src/middleware/requireMfaAuth.ts b/backend/src/middleware/requireMfaAuth.ts index 8fb914258..21b4763c2 100644 --- a/backend/src/middleware/requireMfaAuth.ts +++ b/backend/src/middleware/requireMfaAuth.ts @@ -1,7 +1,7 @@ +import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; import { User } from '../models'; -import { JWT_MFA_SECRET } from '../config'; import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; declare module 'jsonwebtoken' { @@ -26,7 +26,7 @@ const requireMfaAuth = async ( if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) const decodedToken = ( - jwt.verify(AUTH_TOKEN_VALUE, JWT_MFA_SECRET) + jwt.verify(AUTH_TOKEN_VALUE, infisical.get('JWT_MFA_SECRET')!) ); const user = await User.findOne({ diff --git a/backend/src/middleware/requireServiceTokenAuth.ts b/backend/src/middleware/requireServiceTokenAuth.ts index 904f4d38e..ff6bb7c59 100644 --- a/backend/src/middleware/requireServiceTokenAuth.ts +++ b/backend/src/middleware/requireServiceTokenAuth.ts @@ -1,7 +1,7 @@ +import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; import { ServiceToken } from '../models'; -import { JWT_SERVICE_SECRET } from '../config'; import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; // TODO: deprecate @@ -33,7 +33,7 @@ const requireServiceTokenAuth = async ( if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) const decodedToken = ( - jwt.verify(AUTH_TOKEN_VALUE, JWT_SERVICE_SECRET) + jwt.verify(AUTH_TOKEN_VALUE, infisical.get('JWT_SERVICE_SECRET')!) ); const serviceToken = await ServiceToken.findOne({ diff --git a/backend/src/middleware/requireSignupAuth.ts b/backend/src/middleware/requireSignupAuth.ts index 3318bd8d3..deb08d9ae 100644 --- a/backend/src/middleware/requireSignupAuth.ts +++ b/backend/src/middleware/requireSignupAuth.ts @@ -1,7 +1,7 @@ +import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; import { User } from '../models'; -import { JWT_SIGNUP_SECRET } from '../config'; import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; declare module 'jsonwebtoken' { @@ -27,7 +27,7 @@ const requireSignupAuth = async ( if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) const decodedToken = ( - jwt.verify(AUTH_TOKEN_VALUE, JWT_SIGNUP_SECRET) + jwt.verify(AUTH_TOKEN_VALUE, infisical.get('JWT_SIGNUP_SECRET')!) ); const user = await User.findOne({ diff --git a/backend/src/models/token.ts b/backend/src/models/token.ts index c003e42a2..e6f485f55 100644 --- a/backend/src/models/token.ts +++ b/backend/src/models/token.ts @@ -1,5 +1,4 @@ import { Schema, model } from 'mongoose'; -import { EMAIL_TOKEN_LIFETIME } from '../config'; export interface IToken { email: string; diff --git a/backend/src/services/PostHogClient.ts b/backend/src/services/PostHogClient.ts index 0d91a1c13..2d1bb8391 100644 --- a/backend/src/services/PostHogClient.ts +++ b/backend/src/services/PostHogClient.ts @@ -1,27 +1,41 @@ +import infisical from 'infisical-node'; import { PostHog } from 'posthog-node'; -import { - NODE_ENV, - POSTHOG_HOST, - POSTHOG_PROJECT_API_KEY, - TELEMETRY_ENABLED -} from '../config'; import { getLogger } from '../utils/logger'; -if(!TELEMETRY_ENABLED){ - getLogger("backend-main").info([ - "", - "To improve, Infisical collects telemetry data about general usage.", - "This 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 as we support Infisical as open-source software.", - "To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.", - ].join('\n')) +/** + * Logs telemetry enable/disable notice. + */ +const logTelemetryMessage = () => { + const TELEMETRY_ENABLED = infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; + if(!TELEMETRY_ENABLED){ + getLogger("backend-main").info([ + "", + "To improve, Infisical collects telemetry data about general usage.", + "This 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 as we support Infisical as open-source software.", + "To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.", + ].join('\n')) + } } -let postHogClient: any; -if (NODE_ENV === 'production' && TELEMETRY_ENABLED) { - // case: enable opt-out telemetry in production - postHogClient = new PostHog(POSTHOG_PROJECT_API_KEY, { - host: POSTHOG_HOST - }); +/** + * Return an instance of the PostHog client initialized. + * @returns + */ +const getPostHogClient = () => { + let postHogClient: any; + const TELEMETRY_ENABLED = infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; + if (infisical.get('NODE_ENV') === 'production' && TELEMETRY_ENABLED) { + // case: enable opt-out telemetry in production + postHogClient = new PostHog(infisical.get('POSTHOG_PROJECT_API_KEY')! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE', { + host: infisical.get('POSTHOG_HOST')! + }); + } + + return postHogClient; +} + +export { + logTelemetryMessage, + getPostHogClient } -export default postHogClient; diff --git a/backend/src/services/index.ts b/backend/src/services/index.ts index 8ac393cf5..d98b70718 100644 --- a/backend/src/services/index.ts +++ b/backend/src/services/index.ts @@ -1,13 +1,14 @@ import DatabaseService from './DatabaseService'; -import postHogClient from './PostHogClient'; +import { logTelemetryMessage, getPostHogClient } from './PostHogClient'; import BotService from './BotService'; import EventService from './EventService'; import IntegrationService from './IntegrationService'; import TokenService from './TokenService'; export { + logTelemetryMessage, + getPostHogClient, DatabaseService, - postHogClient, BotService, EventService, IntegrationService, diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index 1bf809593..a9c056071 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -1,11 +1,5 @@ +import infisical from 'infisical-node'; import nodemailer from 'nodemailer'; -import { - SMTP_HOST, - SMTP_PORT, - SMTP_USERNAME, - SMTP_PASSWORD, - SMTP_SECURE -} from '../config'; import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN, @@ -15,54 +9,54 @@ import { import SMTPConnection from 'nodemailer/lib/smtp-connection'; import * as Sentry from '@sentry/node'; -const mailOpts: SMTPConnection.Options = { - host: SMTP_HOST, - port: SMTP_PORT as number -}; - -if (SMTP_USERNAME && SMTP_PASSWORD) { - mailOpts.auth = { - user: SMTP_USERNAME, - pass: SMTP_PASSWORD +export const initSmtp = () => { + const mailOpts: SMTPConnection.Options = { + host: infisical.get('SMTP_HOST')!, + port: parseInt(infisical.get('SMTP_PORT')!) }; -} -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; - case SMTP_HOST_SOCKETLABS: - mailOpts.requireTLS = true; - mailOpts.tls = { - ciphers: 'TLSv1.2' - } - break; - case SMTP_HOST_ZOHOMAIL: - mailOpts.requireTLS = true; - mailOpts.tls = { - ciphers: 'TLSv1.2' - } - break; - default: - if (SMTP_HOST.includes('amazonaws.com')) { + if (infisical.get('SMTP_USERNAME')! && infisical.get('SMTP_PASSWORD')!) { + mailOpts.auth = { + user: infisical.get('SMTP_USERNAME')!, + pass: infisical.get('SMTP_PASSWORD')! + }; + } + + if (infisical.get('SMTP_SECURE')! ? infisical.get('SMTP_SECURE')! === 'true' : false) { + switch (infisical.get('SMTP_HOST')!) { + case SMTP_HOST_SENDGRID: + mailOpts.requireTLS = true; + break; + case SMTP_HOST_MAILGUN: + mailOpts.requireTLS = true; mailOpts.tls = { ciphers: 'TLSv1.2' } - } else { - mailOpts.secure = true; - } - break; + break; + case SMTP_HOST_SOCKETLABS: + mailOpts.requireTLS = true; + mailOpts.tls = { + ciphers: 'TLSv1.2' + } + break; + case SMTP_HOST_ZOHOMAIL: + mailOpts.requireTLS = true; + mailOpts.tls = { + ciphers: 'TLSv1.2' + } + break; + default: + if (infisical.get('SMTP_HOST')!.includes('amazonaws.com')) { + mailOpts.tls = { + ciphers: 'TLSv1.2' + } + } else { + mailOpts.secure = true; + } + break; + } } -} -export const initSmtp = () => { const transporter = nodemailer.createTransport(mailOpts); transporter .verify() @@ -73,7 +67,7 @@ export const initSmtp = () => { .catch((err) => { Sentry.setUser(null); Sentry.captureException( - `SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}` + `SMTP - Failed to connect to ${infisical.get('SMTP_HOST')!}:${infisical.get('SMTP_PORT')!} \n\t${err}` ); }); diff --git a/backend/src/utils/addDevelopmentUser.ts b/backend/src/utils/addDevelopmentUser.ts index 2020cf3ad..ed886fd62 100644 --- a/backend/src/utils/addDevelopmentUser.ts +++ b/backend/src/utils/addDevelopmentUser.ts @@ -4,12 +4,12 @@ * ************************************************************************************************/ -import { NODE_ENV } from "../config" +import infisical from 'infisical-node'; import { Key, Membership, MembershipOrg, Organization, User, Workspace } from "../models"; import { Types } from 'mongoose'; export const createTestUserForDevelopment = async () => { - if (NODE_ENV === "development") { + if (infisical.get('NODE_ENV') === "development") { const testUserEmail = "test@localhost.local" const testUserPassword = "testInfisical1" const testUserId = "63cefa6ec8d3175601cfa980" diff --git a/backend/src/utils/logger.ts b/backend/src/utils/logger.ts index 64c65ea49..590a9d950 100644 --- a/backend/src/utils/logger.ts +++ b/backend/src/utils/logger.ts @@ -1,7 +1,7 @@ +import infisical from 'infisical-node'; /* 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; @@ -25,10 +25,10 @@ const createLoggerWithLabel = (level: string, label: string) => { }) ] //* Add LokiTransport if it's enabled - if(LOKI_HOST !== undefined){ + if(infisical.get('LOKI_HOST')! !== undefined){ _transports.push( new LokiTransport({ - host: LOKI_HOST, + host: infisical.get('LOKI_HOST')!, handleExceptions: true, handleRejections: true, batching: true, @@ -37,7 +37,11 @@ const createLoggerWithLabel = (level: string, label: string) => { format: format.combine( format.json() ), - labels: {app: process.env.npm_package_name, version: process.env.npm_package_version, environment: NODE_ENV}, + labels: { + app: process.env.npm_package_name, + version: process.env.npm_package_version, + environment: infisical.get('NODE_ENV')! + }, onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err) }) ) diff --git a/backend/src/utils/requestError.ts b/backend/src/utils/requestError.ts index da2803da7..7e7fd6070 100644 --- a/backend/src/utils/requestError.ts +++ b/backend/src/utils/requestError.ts @@ -1,5 +1,5 @@ +import infisical from 'infisical-node'; import { Request } from 'express' -import { VERBOSE_ERROR_OUTPUT } from '../config' export enum LogLevel { DEBUG = 100, @@ -87,6 +87,7 @@ export default class RequestError extends Error{ }, this.context) //* Omit sensitive information from context that can leak internal workings of this program if user is not developer + const VERBOSE_ERROR_OUTPUT = infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true; if(!VERBOSE_ERROR_OUTPUT){ _context = this._omit(_context, [ 'stacktrace', diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index f7c86e0f8..b71044cba 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -34,7 +34,7 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, - INTEGRATION_OPTIONS, + getIntegrationOptions } from "./integration"; import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from "./organization"; import { SECRET_SHARED, SECRET_PERSONAL } from "./secret"; @@ -113,7 +113,7 @@ export { ACTION_UPDATE_SECRETS, ACTION_DELETE_SECRETS, ACTION_READ_SECRETS, - INTEGRATION_OPTIONS, + getIntegrationOptions, SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN, SMTP_HOST_SOCKETLABS, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 5ed93a597..db8adf105 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -1,13 +1,4 @@ -import { - CLIENT_ID_AZURE, - CLIENT_ID_GITLAB -} from '../config'; -import { - CLIENT_ID_HEROKU, - CLIENT_ID_NETLIFY, - CLIENT_ID_GITHUB, - CLIENT_SLUG_VERCEL -} from "../config"; +import infisical from 'infisical-node'; // integrations const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault'; @@ -48,7 +39,6 @@ const INTEGRATION_GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"; const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token"; - // integration apps endpoints const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com"; const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api"; @@ -59,156 +49,160 @@ const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql"; const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; -// TODO: deprecate types? -const INTEGRATION_OPTIONS = [ - { - name: 'Heroku', - slug: 'heroku', - image: 'Heroku.png', - isAvailable: true, - type: 'oauth', - clientId: CLIENT_ID_HEROKU, - docsLink: '' - }, - { - name: 'Vercel', - slug: 'vercel', - image: 'Vercel.png', - isAvailable: true, - type: 'oauth', - clientId: '', - clientSlug: CLIENT_SLUG_VERCEL, - docsLink: '' - }, - { - name: 'Netlify', - slug: 'netlify', - image: 'Netlify.png', - isAvailable: true, - type: 'oauth', - clientId: CLIENT_ID_NETLIFY, - docsLink: '' - }, - { - name: 'GitHub', - slug: 'github', - image: 'GitHub.png', - isAvailable: true, - type: 'oauth', - clientId: CLIENT_ID_GITHUB, - docsLink: '' - }, - { - name: 'Render', - slug: 'render', - image: 'Render.png', - isAvailable: true, - type: 'pat', - clientId: '', - docsLink: '' - }, - { - name: 'Fly.io', - slug: 'flyio', - image: 'Flyio.svg', - isAvailable: true, - type: 'pat', - clientId: '', - docsLink: '' - }, - { - name: 'AWS Parameter Store', - slug: 'aws-parameter-store', - image: 'Amazon Web Services.png', - isAvailable: true, - type: 'custom', - clientId: '', - docsLink: '' - }, - { - name: 'AWS Secret Manager', - slug: 'aws-secret-manager', - image: 'Amazon Web Services.png', - isAvailable: true, - type: 'custom', - clientId: '', - docsLink: '' - }, - { - name: 'Azure Key Vault', - slug: 'azure-key-vault', - image: 'Microsoft Azure.png', - isAvailable: true, - type: 'oauth', - clientId: CLIENT_ID_AZURE, - docsLink: '' - }, - { - name: 'Circle CI', - slug: 'circleci', - image: 'Circle CI.png', - isAvailable: true, - type: 'pat', - clientId: '', - docsLink: '' - }, - { - name: 'GitLab', - slug: 'gitlab', - image: 'GitLab.png', - isAvailable: true, - type: 'custom', - clientId: CLIENT_ID_GITLAB, - docsLink: '' - }, - { - name: 'Travis CI', - slug: 'travisci', - image: 'Travis CI.png', - isAvailable: true, - type: 'pat', - clientId: '', - docsLink: '' - }, - { - name: 'Google Cloud Platform', - slug: 'gcp', - image: 'Google Cloud Platform.png', - isAvailable: false, - type: '', - clientId: '', - docsLink: '' - } -] +const getIntegrationOptions = () => { + const INTEGRATION_OPTIONS = [ + { + name: 'Heroku', + slug: 'heroku', + image: 'Heroku.png', + isAvailable: true, + type: 'oauth', + clientId: infisical.get('CLIENT_ID_HEROKU')!, + docsLink: '' + }, + { + name: 'Vercel', + slug: 'vercel', + image: 'Vercel.png', + isAvailable: true, + type: 'oauth', + clientId: '', + clientSlug: infisical.get('CLIENT_SLUG_VERCEL')!, + docsLink: '' + }, + { + name: 'Netlify', + slug: 'netlify', + image: 'Netlify.png', + isAvailable: true, + type: 'oauth', + clientId: infisical.get('CLIENT_ID_NETLIFY')!, + docsLink: '' + }, + { + name: 'GitHub', + slug: 'github', + image: 'GitHub.png', + isAvailable: true, + type: 'oauth', + clientId: infisical.get('CLIENT_ID_GITHUB')!, + docsLink: '' + }, + { + name: 'Render', + slug: 'render', + image: 'Render.png', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' + }, + { + name: 'Fly.io', + slug: 'flyio', + image: 'Flyio.svg', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' + }, + { + name: 'AWS Parameter Store', + slug: 'aws-parameter-store', + image: 'Amazon Web Services.png', + isAvailable: true, + type: 'custom', + clientId: '', + docsLink: '' + }, + { + name: 'AWS Secret Manager', + slug: 'aws-secret-manager', + image: 'Amazon Web Services.png', + isAvailable: true, + type: 'custom', + clientId: '', + docsLink: '' + }, + { + name: 'Azure Key Vault', + slug: 'azure-key-vault', + image: 'Microsoft Azure.png', + isAvailable: true, + type: 'oauth', + clientId: infisical.get('CLIENT_ID_AZURE')!, + docsLink: '' + }, + { + name: 'Circle CI', + slug: 'circleci', + image: 'Circle CI.png', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' + }, + { + name: 'GitLab', + slug: 'gitlab', + image: 'GitLab.png', + isAvailable: true, + type: 'custom', + clientId: infisical.get('CLIENT_ID_GITLAB'), + docsLink: '' + }, + { + name: 'Travis CI', + slug: 'travisci', + image: 'Travis CI.png', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' + }, + { + name: 'Google Cloud Platform', + slug: 'gcp', + image: 'Google Cloud Platform.png', + isAvailable: false, + type: '', + clientId: '', + docsLink: '' + } + ] + + return INTEGRATION_OPTIONS; +} + export { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AWS_PARAMETER_STORE, INTEGRATION_AWS_SECRET_MANAGER, - INTEGRATION_HEROKU, - INTEGRATION_VERCEL, - INTEGRATION_NETLIFY, - INTEGRATION_GITHUB, - INTEGRATION_GITLAB, - INTEGRATION_RENDER, - INTEGRATION_FLYIO, - INTEGRATION_CIRCLECI, - INTEGRATION_TRAVISCI, - INTEGRATION_SET, - INTEGRATION_OAUTH2, + INTEGRATION_HEROKU, + INTEGRATION_VERCEL, + INTEGRATION_NETLIFY, + INTEGRATION_GITHUB, + INTEGRATION_GITLAB, + INTEGRATION_RENDER, + INTEGRATION_FLYIO, + INTEGRATION_CIRCLECI, + INTEGRATION_TRAVISCI, + INTEGRATION_SET, + INTEGRATION_OAUTH2, INTEGRATION_AZURE_TOKEN_URL, - INTEGRATION_HEROKU_TOKEN_URL, - INTEGRATION_VERCEL_TOKEN_URL, - INTEGRATION_NETLIFY_TOKEN_URL, - INTEGRATION_GITHUB_TOKEN_URL, - INTEGRATION_GITLAB_API_URL, - INTEGRATION_HEROKU_API_URL, - INTEGRATION_GITLAB_TOKEN_URL, - INTEGRATION_VERCEL_API_URL, - INTEGRATION_NETLIFY_API_URL, - INTEGRATION_RENDER_API_URL, - INTEGRATION_FLYIO_API_URL, - INTEGRATION_CIRCLECI_API_URL, - INTEGRATION_TRAVISCI_API_URL, - INTEGRATION_OPTIONS, + INTEGRATION_HEROKU_TOKEN_URL, + INTEGRATION_VERCEL_TOKEN_URL, + INTEGRATION_NETLIFY_TOKEN_URL, + INTEGRATION_GITHUB_TOKEN_URL, + INTEGRATION_GITLAB_API_URL, + INTEGRATION_HEROKU_API_URL, + INTEGRATION_GITLAB_TOKEN_URL, + INTEGRATION_VERCEL_API_URL, + INTEGRATION_NETLIFY_API_URL, + INTEGRATION_RENDER_API_URL, + INTEGRATION_FLYIO_API_URL, + INTEGRATION_CIRCLECI_API_URL, + INTEGRATION_TRAVISCI_API_URL, + getIntegrationOptions }; diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 342fa9e96..f674c1bcd 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -17,7 +17,6 @@ Configuring Infisical requires setting some environment variables. There is a fi | `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_MFA_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `5m` | -| `EMAIL_TOKEN_LIFETIME` | Email OTP/magic-link lifetime expressed in seconds | `86400` | | `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` | | `MONGO_USERNAME` | MongoDB username if using container | `None` | | `MONGO_PASSWORD` | MongoDB password if using container | `None` | From a6c8638345ea84f519954225b9ea7815ba9e660c Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 15 Mar 2023 00:09:40 +0700 Subject: [PATCH 03/35] Refactor infisical-node to config file for birds eye view of envars --- backend/src/config/index.ts | 150 ++++++------------ backend/src/controllers/v1/authController.ts | 17 +- .../controllers/v1/membershipController.ts | 4 +- .../controllers/v1/membershipOrgController.ts | 8 +- .../controllers/v1/organizationController.ts | 12 +- .../src/controllers/v1/passwordController.ts | 8 +- .../controllers/v1/serviceTokenController.ts | 4 +- .../src/controllers/v1/signupController.ts | 8 +- .../src/controllers/v1/stripeController.ts | 6 +- .../controllers/v2/apiKeyDataController.ts | 4 +- backend/src/controllers/v2/authController.ts | 17 +- .../v2/serviceTokenDataController.ts | 6 +- .../src/controllers/v2/signupController.ts | 6 +- .../src/ee/controllers/v1/stripeController.ts | 6 +- backend/src/helpers/auth.ts | 17 +- backend/src/helpers/bot.ts | 6 +- backend/src/helpers/nodemailer.ts | 4 +- backend/src/helpers/organization.ts | 23 +-- backend/src/helpers/token.ts | 4 +- backend/src/index.ts | 27 ++-- backend/src/integrations/exchange.ts | 47 ++++-- backend/src/integrations/refresh.ts | 28 ++-- backend/src/middleware/requestErrorHandler.ts | 6 +- backend/src/middleware/requireMfaAuth.ts | 4 +- .../src/middleware/requireServiceTokenAuth.ts | 4 +- backend/src/middleware/requireSignupAuth.ts | 4 +- backend/src/services/PostHogClient.ts | 17 +- backend/src/services/smtp.ts | 26 +-- backend/src/utils/addDevelopmentUser.ts | 4 +- backend/src/utils/logger.ts | 8 +- backend/src/utils/requestError.ts | 5 +- backend/src/variables/integration.ts | 21 ++- docs/sdk/overview/usage.mdx | 15 +- 33 files changed, 264 insertions(+), 262 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 06ad3a631..c4e072259 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,101 +1,49 @@ -// const PORT = process.env.PORT || 4000; -// const INVITE_ONLY_SIGNUP = process.env.INVITE_ONLY_SIGNUP == undefined ? false : process.env.INVITE_ONLY_SIGNUP -// const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!; -// const SALT_ROUNDS = parseInt(process.env.SALT_ROUNDS!) || 10; -// const JWT_AUTH_LIFETIME = process.env.JWT_AUTH_LIFETIME! || '10d'; -// const JWT_AUTH_SECRET = process.env.JWT_AUTH_SECRET!; -// const JWT_MFA_LIFETIME = process.env.JWT_MFA_LIFETIME! || '5m'; -// const JWT_MFA_SECRET = process.env.JWT_MFA_SECRET!; -// const JWT_REFRESH_LIFETIME = process.env.JWT_REFRESH_LIFETIME! || '90d'; -// const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!; -// const JWT_SERVICE_SECRET = process.env.JWT_SERVICE_SECRET!; -// const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m'; -// const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; -// const MONGO_URL = process.env.MONGO_URL!; -// 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_ID_AZURE = process.env.CLIENT_ID_AZURE!; -// const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!; -// const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; -// const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!; -// const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!; -// const CLIENT_ID_GITLAB = process.env.CLIENT_ID_GITLAB!; -// const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!; -// const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!; -// const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!; -// const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!; -// const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!; -// const CLIENT_SECRET_GITLAB = process.env.CLIENT_SECRET_GITLAB; -// const CLIENT_SLUG_VERCEL = process.env.CLIENT_SLUG_VERCEL!; -// const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com'; -// const POSTHOG_PROJECT_API_KEY = - // process.env.POSTHOG_PROJECT_API_KEY! || - // 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; -// const SENTRY_DSN = process.env.SENTRY_DSN!; -// const SITE_URL = process.env.SITE_URL!; -// const SMTP_HOST = process.env.SMTP_HOST!; -// const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false; -// const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587; -// const SMTP_USERNAME = process.env.SMTP_USERNAME!; -// const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; -// const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!; -// const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical'; -// const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!; -// const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!; -// const STRIPE_PRODUCT_TEAM = process.env.STRIPE_PRODUCT_TEAM!; -// const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!; -// const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!; -// const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!; -// const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true; - -export { - // PORT, - // INVITE_ONLY_SIGNUP, - // ENCRYPTION_KEY, - // SALT_ROUNDS, - // JWT_AUTH_LIFETIME, - // JWT_AUTH_SECRET, - // JWT_MFA_LIFETIME, - // JWT_MFA_SECRET, - // JWT_REFRESH_LIFETIME, - // JWT_REFRESH_SECRET, - // JWT_SERVICE_SECRET, - // JWT_SIGNUP_LIFETIME, - // JWT_SIGNUP_SECRET, - // MONGO_URL, - // NODE_ENV, - // VERBOSE_ERROR_OUTPUT, - // LOKI_HOST, - // CLIENT_ID_AZURE, - // CLIENT_ID_HEROKU, - // CLIENT_ID_VERCEL, - // CLIENT_ID_NETLIFY, - // CLIENT_ID_GITHUB, - // CLIENT_ID_GITLAB, - // CLIENT_SECRET_AZURE, - // CLIENT_SECRET_HEROKU, - // CLIENT_SECRET_VERCEL, - // CLIENT_SECRET_NETLIFY, - // CLIENT_SECRET_GITHUB, - // CLIENT_SECRET_GITLAB, - // CLIENT_SLUG_VERCEL, - // POSTHOG_HOST, - // POSTHOG_PROJECT_API_KEY, - // SENTRY_DSN, - // SITE_URL, - // SMTP_HOST, - // SMTP_PORT, - // SMTP_SECURE, - // SMTP_USERNAME, - // SMTP_PASSWORD, - // SMTP_FROM_ADDRESS, - // SMTP_FROM_NAME, - // STRIPE_PRODUCT_STARTER, - // STRIPE_PRODUCT_TEAM, - // STRIPE_PRODUCT_PRO, - // STRIPE_PUBLISHABLE_KEY, - // STRIPE_SECRET_KEY, - // STRIPE_WEBHOOK_SECRET, - // TELEMETRY_ENABLED, -}; +import infisical from 'infisical-node'; +export const getPort = () => infisical.get('PORT')! || 4000; +export const getInviteOnlySignup = () => infisical.get('INVITE_ONLY_SIGNUP')! == undefined ? false : process.env.INVITE_ONLY_SIGNUP; +export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!; +export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10; +export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d'; +export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!; +export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')!; +export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m'; +export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d'; +export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!; +export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!; +export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')!; +export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!; +export const getMongoURL = () => infisical.get('MONGO_URL')!; +export const getNodeEnv = () => infisical.get('NODE_ENV')!; +export const getVerboseErrorOutput = () => infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true; +export const getLokiHost = () => infisical.get('LOKI_HOST')!; +export const getClientIdAzure = () => infisical.get('CLIENT_ID_AZURE')!; +export const getClientIdHeroku = () => infisical.get('CLIENT_ID_HEROKU')!; +export const getClientIdVercel = () => infisical.get('CLIENT_ID_VERCEL')!; +export const getClientIdNetlify = () => infisical.get('CLIENT_ID_NETLIFY')!; +export const getClientIdGitHub = () => infisical.get('CLIENT_ID_GITHUB')!; +export const getClientIdGitLab = () => infisical.get('CLIENT_ID_GITLAB')!; +export const getClientSecretAzure = () => infisical.get('CLIENT_SECRET_AZURE')!; +export const getClientSecretHeroku = () => infisical.get('CLIENT_SECRET_HEROKU')!; +export const getClientSecretVercel = () => infisical.get('CLIENT_SECRET_VERCEL')!; +export const getClientSecretNetlify = () => infisical.get('CLIENT_SECRET_NETLIFY')!; +export const getClientSecretGitHub = () => infisical.get('CLIENT_SECRET_GITHUB')!; +export const getClientSecretGitLab = () => infisical.get('CLIENT_SECRET_GITLAB')!; +export const getClientSlugVercel = () => infisical.get('CLIENT_SLUG_VERCEL')!; +export const getPostHogHost = () => infisical.get('POSTHOG_HOST')! || 'https://app.posthog.com'; +export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_KEY')! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; +export const getSentryDSN = () => infisical.get('SENTRY_DSN')!; +export const getSiteURL = () => infisical.get('SITE_URL')!; +export const getSmtpHost = () => infisical.get('SMTP_HOST')!; +export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false; +export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587; +export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!; +export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!; +export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!; +export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical'; +export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!; +export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!; +export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!; +export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_KEY')!; +export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!; +export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!; +export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; \ No newline at end of file diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index 732715967..3c6ec523a 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; import * as bigintConversion from 'bigint-conversion'; @@ -15,6 +14,12 @@ import { import { BadRequestError } from '../../utils/errors'; import { EELogService } from '../../ee/services'; import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this +import { + getNodeEnv, + getJwtRefreshSecret, + getJwtAuthLifetime, + getJwtAuthSecret +} from '../../config'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -121,7 +126,7 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: infisical.get('NODE_ENV')! === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); const loginAction = await EELogService.createAction({ @@ -177,7 +182,7 @@ export const logout = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: infisical.get('NODE_ENV') === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); const logoutAction = await EELogService.createAction({ @@ -232,7 +237,7 @@ export const getNewToken = async (req: Request, res: Response) => { } const decodedToken = ( - jwt.verify(refreshToken, infisical.get('JWT_REFRESH_SECRET')!) + jwt.verify(refreshToken, getJwtRefreshSecret()) ); const user = await User.findOne({ @@ -247,8 +252,8 @@ export const getNewToken = async (req: Request, res: Response) => { payload: { userId: decodedToken.userId }, - expiresIn: infisical.get('JWT_AUTH_LIFETIME')!, - secret: infisical.get('JWT_AUTH_SECRET')! + expiresIn: getJwtAuthLifetime(), + secret: getJwtAuthSecret() }); return res.status(200).send({ diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index 11fc465c2..436be9dc4 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import { Membership, MembershipOrg, User, Key } from '../../models'; @@ -8,6 +7,7 @@ import { } from '../../helpers/membership'; import { sendMail } from '../../helpers/nodemailer'; import { ADMIN, MEMBER, ACCEPTED } from '../../variables'; +import { getSiteURL } from '../../config'; /** * Check that user is a member of workspace with id [workspaceId] @@ -215,7 +215,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => { inviterFirstName: req.user.firstName, inviterEmail: req.user.email, workspaceName: req.membership.workspace.name, - callback_url: infisical.get('SITE_URL')! + '/login' + callback_url: getSiteURL() + '/login' } }); } catch (err) { diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index 0efe00dac..105be0503 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import { MembershipOrg, Organization, User } from '../../models'; @@ -8,6 +7,7 @@ import { updateSubscriptionOrgQuantity } from '../../helpers/organization'; import { sendMail } from '../../helpers/nodemailer'; import { TokenService } from '../../services'; import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables'; +import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; /** * Delete organization membership with id [membershipOrgId] from organization @@ -178,7 +178,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { organizationName: organization.name, email: inviteeEmail, token, - callback_url: infisical.get('SITE_URL') + '/signupinvite' + callback_url: getSiteURL() + '/signupinvite' } }); } @@ -250,8 +250,8 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: infisical.get('JWT_SIGNUP_LIFETIME')!, - secret: infisical.get('JWT_SIGNUP_SECRET')! + expiresIn: getJwtSignupLifetime(), + secret: getJwtSignupSecret() }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/organizationController.ts b/backend/src/controllers/v1/organizationController.ts index 44403db86..00ad87b82 100644 --- a/backend/src/controllers/v1/organizationController.ts +++ b/backend/src/controllers/v1/organizationController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import { Request, Response } from 'express'; import Stripe from 'stripe'; @@ -13,6 +12,7 @@ import { createOrganization as create } from '../../helpers/organization'; import { addMembershipsOrg } from '../../helpers/membershipOrg'; import { OWNER, ACCEPTED } from '../../variables'; import _ from 'lodash'; +import { getStripeSecretKey, getSiteURL } from '../../config'; export const getOrganizations = async (req: Request, res: Response) => { let organizations; @@ -317,7 +317,7 @@ export const createOrganizationPortalSession = async ( ) => { let session; try { - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); @@ -333,13 +333,13 @@ export const createOrganizationPortalSession = async ( customer: req.membershipOrg.organization.customerId, mode: 'setup', payment_method_types: ['card'], - success_url: infisical.get('SITE_URL')! + '/dashboard', - cancel_url: infisical.get('SITE_URL')! + '/dashboard' + success_url: getSiteURL() + '/dashboard', + cancel_url: getSiteURL() + '/dashboard' }); } else { session = await stripe.billingPortal.sessions.create({ customer: req.membershipOrg.organization.customerId, - return_url: infisical.get('SITE_URL') + '/dashboard' + return_url: getSiteURL() + '/dashboard' }); } @@ -365,7 +365,7 @@ export const getOrganizationSubscriptions = async ( ) => { let subscriptions; try { - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); diff --git a/backend/src/controllers/v1/passwordController.ts b/backend/src/controllers/v1/passwordController.ts index 72a649bb1..fed24419c 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -10,6 +9,7 @@ import { sendMail } from '../../helpers/nodemailer'; import { TokenService } from '../../services'; import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables'; import { BadRequestError } from '../../utils/errors'; +import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; /** * Password reset step 1: Send email verification link to email [email] @@ -44,7 +44,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => { substitutions: { email, token, - callback_url: infisical.get('SITE_URL')! + '/password-reset' + callback_url: getSiteURL() + '/password-reset' } }); } catch (err) { @@ -91,8 +91,8 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: infisical.get('JWT_SIGNUP_LIFETIME')!, - secret: infisical.get('JWT_SIGNUP_SECRET')! + expiresIn: getJwtSignupLifetime(), + secret: getJwtSignupSecret() }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/serviceTokenController.ts b/backend/src/controllers/v1/serviceTokenController.ts index 76899a1a7..9f241349a 100644 --- a/backend/src/controllers/v1/serviceTokenController.ts +++ b/backend/src/controllers/v1/serviceTokenController.ts @@ -1,7 +1,7 @@ -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import { ServiceToken } from '../../models'; import { createToken } from '../../helpers/auth'; +import { getJwtServiceSecret } from '../../config'; /** * Return service token on request @@ -61,7 +61,7 @@ export const createServiceToken = async (req: Request, res: Response) => { workspaceId }, expiresIn: expiresIn, - secret: infisical.get('JWT_SERVICE_SECRET')! + secret: getJwtServiceSecret() }); } catch (err) { return res.status(400).send({ diff --git a/backend/src/controllers/v1/signupController.ts b/backend/src/controllers/v1/signupController.ts index dbf5bd5a4..cb411a29e 100644 --- a/backend/src/controllers/v1/signupController.ts +++ b/backend/src/controllers/v1/signupController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import { User } from '../../models'; @@ -8,6 +7,7 @@ import { } from '../../helpers/signup'; import { createToken } from '../../helpers/auth'; import { BadRequestError } from '../../utils/errors'; +import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; /** * Signup step 1: Initialize account for user under email [email] and send a verification code @@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => { try { email = req.body.email; - if (infisical.get('INVITE_ONLY_SIGNUP') || false) { + if (getInviteOnlySignup() || false) { // Only one user can create an account without being invited. The rest need to be invited in order to make an account const userCount = await User.countDocuments({}) if (userCount != 0) { @@ -91,8 +91,8 @@ export const verifyEmailSignup = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: infisical.get('JWT_SIGNUP_LIFETIME')!, - secret: infisical.get('JWT_SIGNUP_SECRET')! + expiresIn: getJwtSignupLifetime(), + secret: getJwtSignupSecret() }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/controllers/v1/stripeController.ts b/backend/src/controllers/v1/stripeController.ts index 1d3ee0bf2..1a981c088 100644 --- a/backend/src/controllers/v1/stripeController.ts +++ b/backend/src/controllers/v1/stripeController.ts @@ -1,7 +1,7 @@ -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import Stripe from 'stripe'; +import { getStripeSecretKey, getStripeWebhookSecret } from '../../config'; /** * Handle service provisioning/un-provisioning via Stripe @@ -13,7 +13,7 @@ export const handleWebhook = async (req: Request, res: Response) => { let event; try { // check request for valid stripe signature - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); @@ -21,7 +21,7 @@ export const handleWebhook = async (req: Request, res: Response) => { event = stripe.webhooks.constructEvent( req.body, sig, - infisical.get('STRIPE_WEBHOOK_SECRET')! + getStripeWebhookSecret() ); } catch (err) { Sentry.setUser({ email: req.user.email }); diff --git a/backend/src/controllers/v2/apiKeyDataController.ts b/backend/src/controllers/v2/apiKeyDataController.ts index b6819928a..fd87f7306 100644 --- a/backend/src/controllers/v2/apiKeyDataController.ts +++ b/backend/src/controllers/v2/apiKeyDataController.ts @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; import { APIKeyData } from '../../models'; +import { getSaltRounds } from '../../config'; /** * Return API key data for user with id [req.user_id] @@ -43,7 +43,7 @@ export const createAPIKeyData = async (req: Request, res: Response) => { const { name, expiresIn } = req.body; const secret = crypto.randomBytes(16).toString('hex'); - const secretHash = await bcrypt.hash(secret, parseInt(infisical.get('SALT_ROUNDS')!) || 10); + const secretHash = await bcrypt.hash(secret, getSaltRounds()); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); diff --git a/backend/src/controllers/v2/authController.ts b/backend/src/controllers/v2/authController.ts index 4401e2498..b0204e1b9 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; /* eslint-disable @typescript-eslint/no-var-requires */ import { Request, Response } from 'express'; import jwt from 'jsonwebtoken'; @@ -17,6 +16,11 @@ import { ACTION_LOGIN } from '../../variables'; import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this +import { + getNodeEnv, + getJwtMfaLifetime, + getJwtMfaSecret +} from '../../config'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -120,8 +124,8 @@ export const login2 = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: infisical.get('JWT_MFA_LIFETIME')!, - secret: infisical.get('JWT_MFA_SECRET')! + expiresIn: getJwtMfaLifetime(), + secret: getJwtMfaSecret() }); const code = await TokenService.createToken({ @@ -159,7 +163,7 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: infisical.get('NODE_ENV')! === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); // case: user does not have MFA enablgged @@ -298,7 +302,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: infisical.get('NODE_ENV')! === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); interface VerifyMfaTokenRes { @@ -341,5 +345,4 @@ export const verifyMfaToken = async (req: Request, res: Response) => { }); return res.status(200).send(resObj); -} - +} \ No newline at end of file diff --git a/backend/src/controllers/v2/serviceTokenDataController.ts b/backend/src/controllers/v2/serviceTokenDataController.ts index 295664cf9..a4e06f8e4 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; @@ -8,6 +7,7 @@ import { } from '../../models'; import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions'; import { ABILITY_READ } from '../../variables/organization'; +import { getSaltRounds } from '../../config'; /** * Return service token data associated with service token on request @@ -73,7 +73,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => { } const secret = crypto.randomBytes(16).toString('hex'); - const secretHash = await bcrypt.hash(secret, parseInt(infisical.get('SALT_ROUNDS')!) || 10); + const secretHash = await bcrypt.hash(secret, getSaltRounds()); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); @@ -140,4 +140,4 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => { function UnauthorizedRequestError(arg0: { message: string; }) { throw new Error('Function not implemented.'); -} +} \ No newline at end of file diff --git a/backend/src/controllers/v2/signupController.ts b/backend/src/controllers/v2/signupController.ts index 0eb148e37..3e4b1f44a 100644 --- a/backend/src/controllers/v2/signupController.ts +++ b/backend/src/controllers/v2/signupController.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import * as Sentry from '@sentry/node'; import { User, MembershipOrg } from '../../models'; @@ -9,6 +8,7 @@ import { import { issueAuthTokens } from '../../helpers/auth'; import { INVITED, ACCEPTED } from '../../variables'; import request from '../../config/request'; +import { getNodeEnv } from '../../config'; /** * Complete setting up user by adding their personal and auth information as part of the @@ -127,7 +127,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: infisical.get('NODE_ENV')! === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); } catch (err) { Sentry.setUser(null); @@ -232,7 +232,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: infisical.get('NODE_ENV')! === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/ee/controllers/v1/stripeController.ts b/backend/src/ee/controllers/v1/stripeController.ts index 7c816550e..3caa0f395 100644 --- a/backend/src/ee/controllers/v1/stripeController.ts +++ b/backend/src/ee/controllers/v1/stripeController.ts @@ -1,7 +1,7 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import { Request, Response } from 'express'; import Stripe from 'stripe'; +import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config'; /** * Handle service provisioning/un-provisioning via Stripe @@ -12,7 +12,7 @@ import Stripe from 'stripe'; export const handleWebhook = async (req: Request, res: Response) => { let event; try { - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); @@ -21,7 +21,7 @@ export const handleWebhook = async (req: Request, res: Response) => { event = stripe.webhooks.constructEvent( req.body, sig, - infisical.get('STRIPE_WEBHOOK_SECRET')! + getStripeWebhookSecret() ); } catch (err) { Sentry.setUser({ email: req.user.email }); diff --git a/backend/src/helpers/auth.ts b/backend/src/helpers/auth.ts index 680120395..a08dcf2cc 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import bcrypt from 'bcrypt'; import { @@ -15,6 +14,12 @@ import { UnauthorizedRequestError, BadRequestError } from '../utils/errors'; +import { + getJwtAuthLifetime, + getJwtAuthSecret, + getJwtRefreshLifetime, + getJwtRefreshSecret +} from '../config'; /** * @@ -88,7 +93,7 @@ const getAuthUserPayload = async ({ let user; try { const decodedToken = ( - jwt.verify(authTokenValue, infisical.get('JWT_AUTH_SECRET')!) + jwt.verify(authTokenValue, getJwtAuthSecret()) ); user = await User.findOne({ @@ -219,16 +224,16 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => { payload: { userId }, - expiresIn: infisical.get('JWT_AUTH_LIFETIME')!, - secret: infisical.get('JWT_AUTH_SECRET')! + expiresIn: getJwtAuthLifetime(), + secret: getJwtAuthSecret() }); refreshToken = createToken({ payload: { userId }, - expiresIn: infisical.get('JWT_REFRESH_LIFETIME')!, - secret: infisical.get('JWT_REFRESH_SECRET')! + expiresIn: getJwtRefreshLifetime(), + secret: getJwtRefreshSecret() }); } catch (err) { Sentry.setUser(null); diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index 8912befd1..5cfbeebf5 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import { Bot, BotKey, @@ -14,6 +13,7 @@ import { decryptAsymmetric } from '../utils/crypto'; import { SECRET_SHARED } from '../variables'; +import { getEncryptionKey } from '../config'; /** * Create an inactive bot with name [name] for workspace with id [workspaceId] @@ -33,7 +33,7 @@ const createBot = async ({ const { publicKey, privateKey } = generateKeyPair(); const { ciphertext, iv, tag } = encryptSymmetric({ plaintext: privateKey, - key: infisical.get('ENCRYPTION_KEY')! + key: getEncryptionKey() }); bot = await new Bot({ @@ -130,7 +130,7 @@ const getKey = async ({ workspaceId }: { workspaceId: string }) => { ciphertext: bot.encryptedPrivateKey, iv: bot.iv, tag: bot.tag, - key: infisical.get('ENCRYPTION_KEY')! + key: getEncryptionKey() }); key = decryptAsymmetric({ diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 45fce5d1d..d765f3200 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -1,9 +1,9 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; import nodemailer from 'nodemailer'; +import { getSmtpFromName, getSmtpFromAddress } from '../config'; let smtpTransporter: nodemailer.Transporter; @@ -34,7 +34,7 @@ const sendMail = async ({ const htmlToSend = temp(substitutions); await smtpTransporter.sendMail({ - from: `"${infisical.get('SMTP_FROM_NAME')!}" <${infisical.get('SMTP_FROM_ADDRESS')!}>`, + from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, to: recipients.join(', '), subject: subjectLine, html: htmlToSend diff --git a/backend/src/helpers/organization.ts b/backend/src/helpers/organization.ts index 87711da92..fb559df1b 100644 --- a/backend/src/helpers/organization.ts +++ b/backend/src/helpers/organization.ts @@ -1,9 +1,14 @@ -import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import Stripe from 'stripe'; import { Types } from 'mongoose'; import { ACCEPTED } from '../variables'; import { Organization, MembershipOrg } from '../models'; +import { + getStripeSecretKey, + getStripeProductPro, + getStripeProductTeam, + getStripeProductStarter +} from '../config'; /** * Create an organization with name [name] @@ -22,11 +27,11 @@ const createOrganization = async ({ let organization; try { // register stripe account - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); - if (infisical.get('STRIPE_SECRET_KEY')) { + if (getStripeSecretKey()) { const customer = await stripe.customers.create({ email, description: name @@ -76,14 +81,14 @@ const initSubscriptionOrg = async ({ if (organization) { if (organization.customerId) { // initialize starter subscription with quantity of 0 - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); const productToPriceMap = { - starter: infisical.get('STRIPE_PRODUCT_STARTER')!, - team: infisical.get('STRIPE_PRODUCT_TEAM')!, - pro: infisical.get('STRIPE_PRODUCT_PRO')! + starter: getStripeProductStarter(), + team: getStripeProductTeam(), + pro: getStripeProductPro() }; stripeSubscription = await stripe.subscriptions.create({ @@ -138,7 +143,7 @@ const updateSubscriptionOrgQuantity = async ({ status: ACCEPTED }); - const stripe = new Stripe(infisical.get('STRIPE_SECRET_KEY')!, { + const stripe = new Stripe(getStripeSecretKey(), { apiVersion: '2022-08-01' }); @@ -170,4 +175,4 @@ export { createOrganization, initSubscriptionOrg, updateSubscriptionOrgQuantity -}; +}; \ No newline at end of file diff --git a/backend/src/helpers/token.ts b/backend/src/helpers/token.ts index b2365cb2c..0f6a88cc9 100644 --- a/backend/src/helpers/token.ts +++ b/backend/src/helpers/token.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/node'; -import infisical from 'infisical-node'; import { Types } from 'mongoose'; import { TokenData } from '../models'; import crypto from 'crypto'; @@ -11,6 +10,7 @@ import { TOKEN_EMAIL_PASSWORD_RESET } from '../variables'; import { UnauthorizedRequestError } from '../utils/errors'; +import { getSaltRounds } from '../config'; /** * Create and store a token in the database for purpose [type] @@ -84,7 +84,7 @@ const createTokenHelper = async ({ const query: TokenDataQuery = { type }; const update: TokenDataUpdate = { type, - tokenHash: await bcrypt.hash(token, parseInt(infisical.get('SALT_ROUNDS')!) || 10), + tokenHash: await bcrypt.hash(token, getSaltRounds()), expiresAt } diff --git a/backend/src/index.ts b/backend/src/index.ts index 24172fc89..5647ad870 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -63,8 +63,17 @@ import { healthCheck } from './routes/status'; import { getLogger } from './utils/logger'; import { RouteNotFoundError } from './utils/errors'; import { requestErrorHandler } from './middleware/requestErrorHandler'; +import { + getMongoURL, + getNodeEnv, + getPort, + getSentryDSN, + getSiteURL +} from './config'; const main = async () => { + // TODO 1: handle case of empty string token + // TODO 2: handle case of undefined token const client = await infisical.connect({ token: process.env.INFISICAL_TOKEN!, debug: true @@ -73,13 +82,13 @@ const main = async () => { logTelemetryMessage(); setTransporter(initSmtp()); - await DatabaseService.initDatabase(infisical.get('MONGO_URL')!); - if (infisical.get('NODE_ENV') !== 'test') { + await DatabaseService.initDatabase(getMongoURL()); + if (getNodeEnv() !== 'test') { Sentry.init({ - dsn: infisical.get('SENTRY_DSN') as string, + dsn: getSentryDSN(), tracesSampleRate: 1.0, - debug: infisical.get('NODE_ENV') === 'production' ? false : true, - environment: infisical.get('NODE_ENV') as string + debug: getNodeEnv() === 'production' ? false : true, + environment: getNodeEnv() }); } @@ -91,13 +100,13 @@ const main = async () => { app.use( cors({ credentials: true, - origin: infisical.get('SITE_URL') as string + origin: getSiteURL() }) ); app.use(requestIp.mw()); - if (infisical.get('NODE_ENV') === 'production') { + if (getNodeEnv() === 'production') { // enable app-wide rate-limiting + helmet security // in production app.disable('x-powered-by'); @@ -157,9 +166,9 @@ const main = async () => { app.use(requestErrorHandler) - const server = app.listen(Number(infisical.get('PORT')) || 4000, () => { + const server = app.listen(getPort(), () => { createTestUserForDevelopment(); - getLogger("backend-main").info(`Server started listening at port ${Number(infisical.get('PORT')) || 4000}`) + getLogger("backend-main").info(`Server started listening at port ${getPort()}`) }); setUpHealthEndpoint(server); diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 4e89e3aa9..1dccb03d0 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import request from '../config/request'; import { @@ -15,6 +14,20 @@ import { INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL } from '../variables'; +import { + getSiteURL, + getClientIdAzure, + getClientSecretAzure, + getClientSecretHeroku, + getClientIdVercel, + getClientSecretVercel, + getClientIdNetlify, + getClientSecretNetlify, + getClientIdGitHub, + getClientSecretGitHub, + getClientIdGitLab, + getClientSecretGitLab +} from '../config'; interface ExchangeCodeAzureResponse { token_type: string; @@ -146,9 +159,9 @@ const exchangeCodeAzure = async ({ grant_type: 'authorization_code', code: code, scope: 'https://vault.azure.net/.default openid offline_access', - client_id: infisical.get('CLIENT_ID_AZURE')!, - client_secret: infisical.get('CLIENT_SECRET_AZURE')!, - redirect_uri: `${infisical.get('SITE_URL')!}/integrations/azure-key-vault/oauth2/callback` + client_id: getClientIdAzure(), + client_secret: getClientSecretAzure(), + redirect_uri: `${getSiteURL()}/integrations/azure-key-vault/oauth2/callback` } as any) )).data; @@ -191,7 +204,7 @@ const exchangeCodeHeroku = async ({ new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_secret: infisical.get('CLIENT_SECRET_HEROKU')! + client_secret: getClientSecretHeroku() } as any) )).data; @@ -229,9 +242,9 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => { INTEGRATION_VERCEL_TOKEN_URL, new URLSearchParams({ code: code, - client_id: infisical.get('CLIENT_ID_VERCEL')!, - client_secret: infisical.get('CLIENT_SECRET_VERCEL')!, - redirect_uri: `${infisical.get('SITE_URL')!}/integrations/vercel/oauth2/callback` + client_id: getClientIdVercel(), + client_secret: getClientSecretVercel(), + redirect_uri: `${getSiteURL()}/integrations/vercel/oauth2/callback` } as any) ) ).data; @@ -269,9 +282,9 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => { new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_id: infisical.get('CLIENT_ID_NETLIFY')!, - client_secret: infisical.get('CLIENT_SECRET_NETLIFY')!, - redirect_uri: `${infisical.get('SITE_URL')!}/integrations/netlify/oauth2/callback` + client_id: getClientIdNetlify(), + client_secret: getClientSecretNetlify(), + redirect_uri: `${getSiteURL()}/integrations/netlify/oauth2/callback` } as any) ) ).data; @@ -320,10 +333,10 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => { res = ( await request.get(INTEGRATION_GITHUB_TOKEN_URL, { params: { - client_id: infisical.get('CLIENT_ID_GITHUB')!, - client_secret: infisical.get('CLIENT_SECRET_GITHUB')!, + client_id: getClientIdGitHub(), + client_secret: getClientSecretGitHub(), code: code, - redirect_uri: `${infisical.get('SITE_URL')!}/integrations/github/oauth2/callback` + redirect_uri: `${getSiteURL()}/integrations/github/oauth2/callback` }, headers: { 'Accept': 'application/json', @@ -366,9 +379,9 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => { new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_id: infisical.get('CLIENT_ID_GITLAB')!, - client_secret: infisical.get('CLIENT_SECRET_GITLAB')!, - redirect_uri: `${infisical.get('SITE_URL')}/integrations/gitlab/oauth2/callback` + client_id: getClientIdGitLab(), + client_secret: getClientSecretGitLab(), + redirect_uri: `${getSiteURL()}/integrations/gitlab/oauth2/callback` } as any), { headers: { diff --git a/backend/src/integrations/refresh.ts b/backend/src/integrations/refresh.ts index 67cd382c1..a0aea080e 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import request from '../config/request'; import { @@ -9,13 +8,6 @@ import { INTEGRATION_HEROKU, INTEGRATION_GITLAB, } from '../variables'; -// import { -// CLIENT_ID_AZURE, -// CLIENT_ID_GITLAB, -// CLIENT_SECRET_AZURE, -// CLIENT_SECRET_HEROKU, -// CLIENT_SECRET_GITLAB -// } from '../config'; import { INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL, @@ -24,6 +16,14 @@ import { import { IntegrationService } from '../services'; +import { + getSiteURL, + getClientIdAzure, + getClientSecretAzure, + getClientSecretHeroku, + getClientIdGitLab, + getClientSecretGitLab +} from '../config'; interface RefreshTokenAzureResponse { token_type: string; @@ -133,11 +133,11 @@ const exchangeRefreshAzure = async ({ const { data }: { data: RefreshTokenAzureResponse } = await request.post( INTEGRATION_AZURE_TOKEN_URL, new URLSearchParams({ - client_id: infisical.get('CLIENT_ID_AZURE')!, + client_id: getClientIdAzure(), scope: 'openid offline_access', refresh_token: refreshToken, grant_type: 'refresh_token', - client_secret: infisical.get('CLIENT_SECRET_AZURE')! + client_secret: getClientSecretAzure() } as any) ); @@ -180,7 +180,7 @@ const exchangeRefreshHeroku = async ({ new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, - client_secret: infisical.get('CLIENT_SECRET_HEROKU')! + client_secret: getClientSecretHeroku() } as any) ); @@ -223,9 +223,9 @@ const exchangeRefreshGitLab = async ({ new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, - client_id: infisical.get('CLIENT_ID_GITLAB')!, - client_secret: infisical.get('CLIENT_SECRET_GITLAB')!, - redirect_uri: `${infisical.get('SITE_URL')!}/integrations/gitlab/oauth2/callback` + client_id: getClientIdGitLab, + client_secret: getClientSecretGitLab(), + redirect_uri: `${getSiteURL()}/integrations/gitlab/oauth2/callback` } as any), { headers: { diff --git a/backend/src/middleware/requestErrorHandler.ts b/backend/src/middleware/requestErrorHandler.ts index 2a881e814..202a38e83 100644 --- a/backend/src/middleware/requestErrorHandler.ts +++ b/backend/src/middleware/requestErrorHandler.ts @@ -1,13 +1,13 @@ -import infisical from 'infisical-node'; import * as Sentry from '@sentry/node'; import { ErrorRequestHandler } from "express"; -import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; +import { InternalServerError } from "../utils/errors"; import { getLogger } from "../utils/logger"; import RequestError, { LogLevel } from "../utils/requestError"; +import { getNodeEnv } from '../config'; export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => { if (res.headersSent) return next(); - if (infisical.get('NODE_ENV')! !== "production") { + if (getNodeEnv() !== "production") { /* eslint-disable no-console */ console.log(error) /* eslint-enable no-console */ diff --git a/backend/src/middleware/requireMfaAuth.ts b/backend/src/middleware/requireMfaAuth.ts index 21b4763c2..7fb38ca25 100644 --- a/backend/src/middleware/requireMfaAuth.ts +++ b/backend/src/middleware/requireMfaAuth.ts @@ -1,8 +1,8 @@ -import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; import { User } from '../models'; import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; +import { getJwtMfaSecret } from '../config'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -26,7 +26,7 @@ const requireMfaAuth = async ( if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) const decodedToken = ( - jwt.verify(AUTH_TOKEN_VALUE, infisical.get('JWT_MFA_SECRET')!) + jwt.verify(AUTH_TOKEN_VALUE, getJwtMfaSecret()) ); const user = await User.findOne({ diff --git a/backend/src/middleware/requireServiceTokenAuth.ts b/backend/src/middleware/requireServiceTokenAuth.ts index ff6bb7c59..106ca9bbb 100644 --- a/backend/src/middleware/requireServiceTokenAuth.ts +++ b/backend/src/middleware/requireServiceTokenAuth.ts @@ -1,8 +1,8 @@ -import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; import { ServiceToken } from '../models'; import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; +import { getJwtServiceSecret } from '../config'; // TODO: deprecate declare module 'jsonwebtoken' { @@ -33,7 +33,7 @@ const requireServiceTokenAuth = async ( if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) const decodedToken = ( - jwt.verify(AUTH_TOKEN_VALUE, infisical.get('JWT_SERVICE_SECRET')!) + jwt.verify(AUTH_TOKEN_VALUE, getJwtServiceSecret()) ); const serviceToken = await ServiceToken.findOne({ diff --git a/backend/src/middleware/requireSignupAuth.ts b/backend/src/middleware/requireSignupAuth.ts index deb08d9ae..19e6b3146 100644 --- a/backend/src/middleware/requireSignupAuth.ts +++ b/backend/src/middleware/requireSignupAuth.ts @@ -1,8 +1,8 @@ -import infisical from 'infisical-node'; import jwt from 'jsonwebtoken'; import { Request, Response, NextFunction } from 'express'; import { User } from '../models'; import { BadRequestError, UnauthorizedRequestError } from '../utils/errors'; +import { getJwtSignupSecret } from '../config'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -27,7 +27,7 @@ const requireSignupAuth = async ( if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'})) const decodedToken = ( - jwt.verify(AUTH_TOKEN_VALUE, infisical.get('JWT_SIGNUP_SECRET')!) + jwt.verify(AUTH_TOKEN_VALUE, getJwtSignupSecret()) ); const user = await User.findOne({ diff --git a/backend/src/services/PostHogClient.ts b/backend/src/services/PostHogClient.ts index 2d1bb8391..15ccf0919 100644 --- a/backend/src/services/PostHogClient.ts +++ b/backend/src/services/PostHogClient.ts @@ -1,13 +1,17 @@ -import infisical from 'infisical-node'; import { PostHog } from 'posthog-node'; import { getLogger } from '../utils/logger'; +import { + getNodeEnv, + getTelemetryEnabled, + getPostHogProjectApiKey, + getPostHogHost +} from '../config'; /** * Logs telemetry enable/disable notice. */ const logTelemetryMessage = () => { - const TELEMETRY_ENABLED = infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; - if(!TELEMETRY_ENABLED){ + if(!getTelemetryEnabled()){ getLogger("backend-main").info([ "", "To improve, Infisical collects telemetry data about general usage.", @@ -23,11 +27,10 @@ const logTelemetryMessage = () => { */ const getPostHogClient = () => { let postHogClient: any; - const TELEMETRY_ENABLED = infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; - if (infisical.get('NODE_ENV') === 'production' && TELEMETRY_ENABLED) { + if (getNodeEnv() === 'production' && getTelemetryEnabled()) { // case: enable opt-out telemetry in production - postHogClient = new PostHog(infisical.get('POSTHOG_PROJECT_API_KEY')! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE', { - host: infisical.get('POSTHOG_HOST')! + postHogClient = new PostHog(getPostHogProjectApiKey(), { + host: getPostHogHost() }); } diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index a9c056071..7a4ebf00b 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -1,4 +1,3 @@ -import infisical from 'infisical-node'; import nodemailer from 'nodemailer'; import { SMTP_HOST_SENDGRID, @@ -8,22 +7,29 @@ import { } from '../variables'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; import * as Sentry from '@sentry/node'; +import { + getSmtpHost, + getSmtpUsername, + getSmtpPassword, + getSmtpSecure, + getSmtpPort +} from '../config'; export const initSmtp = () => { const mailOpts: SMTPConnection.Options = { - host: infisical.get('SMTP_HOST')!, - port: parseInt(infisical.get('SMTP_PORT')!) + host: getSmtpHost(), + port: getSmtpPort() }; - if (infisical.get('SMTP_USERNAME')! && infisical.get('SMTP_PASSWORD')!) { + if (getSmtpUsername() && getSmtpPassword()) { mailOpts.auth = { - user: infisical.get('SMTP_USERNAME')!, - pass: infisical.get('SMTP_PASSWORD')! + user: getSmtpUsername(), + pass: getSmtpPassword() }; } - if (infisical.get('SMTP_SECURE')! ? infisical.get('SMTP_SECURE')! === 'true' : false) { - switch (infisical.get('SMTP_HOST')!) { + if (getSmtpSecure() ? getSmtpSecure() : false) { + switch (getSmtpHost()) { case SMTP_HOST_SENDGRID: mailOpts.requireTLS = true; break; @@ -46,7 +52,7 @@ export const initSmtp = () => { } break; default: - if (infisical.get('SMTP_HOST')!.includes('amazonaws.com')) { + if (getSmtpHost().includes('amazonaws.com')) { mailOpts.tls = { ciphers: 'TLSv1.2' } @@ -67,7 +73,7 @@ export const initSmtp = () => { .catch((err) => { Sentry.setUser(null); Sentry.captureException( - `SMTP - Failed to connect to ${infisical.get('SMTP_HOST')!}:${infisical.get('SMTP_PORT')!} \n\t${err}` + `SMTP - Failed to connect to ${getSmtpHost()}:${getSmtpPort()} \n\t${err}` ); }); diff --git a/backend/src/utils/addDevelopmentUser.ts b/backend/src/utils/addDevelopmentUser.ts index ed886fd62..585740a6b 100644 --- a/backend/src/utils/addDevelopmentUser.ts +++ b/backend/src/utils/addDevelopmentUser.ts @@ -4,12 +4,12 @@ * ************************************************************************************************/ -import infisical from 'infisical-node'; import { Key, Membership, MembershipOrg, Organization, User, Workspace } from "../models"; import { Types } from 'mongoose'; +import { getNodeEnv } from '../config'; export const createTestUserForDevelopment = async () => { - if (infisical.get('NODE_ENV') === "development") { + if (getNodeEnv() === "development") { const testUserEmail = "test@localhost.local" const testUserPassword = "testInfisical1" const testUserId = "63cefa6ec8d3175601cfa980" diff --git a/backend/src/utils/logger.ts b/backend/src/utils/logger.ts index 590a9d950..ed29c97ca 100644 --- a/backend/src/utils/logger.ts +++ b/backend/src/utils/logger.ts @@ -1,7 +1,7 @@ -import infisical from 'infisical-node'; /* eslint-disable no-console */ import { createLogger, format, transports } from 'winston'; import LokiTransport from 'winston-loki'; +import { getLokiHost, getNodeEnv } from '../config'; const { combine, colorize, label, printf, splat, timestamp } = format; @@ -25,10 +25,10 @@ const createLoggerWithLabel = (level: string, label: string) => { }) ] //* Add LokiTransport if it's enabled - if(infisical.get('LOKI_HOST')! !== undefined){ + if(getLokiHost() !== undefined){ _transports.push( new LokiTransport({ - host: infisical.get('LOKI_HOST')!, + host: getLokiHost(), handleExceptions: true, handleRejections: true, batching: true, @@ -40,7 +40,7 @@ const createLoggerWithLabel = (level: string, label: string) => { labels: { app: process.env.npm_package_name, version: process.env.npm_package_version, - environment: infisical.get('NODE_ENV')! + environment: getNodeEnv() }, onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err) }) diff --git a/backend/src/utils/requestError.ts b/backend/src/utils/requestError.ts index 7e7fd6070..4b5635bac 100644 --- a/backend/src/utils/requestError.ts +++ b/backend/src/utils/requestError.ts @@ -1,5 +1,5 @@ -import infisical from 'infisical-node'; import { Request } from 'express' +import { getVerboseErrorOutput } from '../config'; export enum LogLevel { DEBUG = 100, @@ -87,8 +87,7 @@ export default class RequestError extends Error{ }, this.context) //* Omit sensitive information from context that can leak internal workings of this program if user is not developer - const VERBOSE_ERROR_OUTPUT = infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true; - if(!VERBOSE_ERROR_OUTPUT){ + if(!getVerboseErrorOutput()){ _context = this._omit(_context, [ 'stacktrace', 'exception', diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index db8adf105..52bfcc614 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -1,4 +1,11 @@ -import infisical from 'infisical-node'; +import { + getClientIdHeroku, + getClientSlugVercel, + getClientIdNetlify, + getClientIdAzure, + getClientIdGitLab, + getClientIdGitHub +} from '../config'; // integrations const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault'; @@ -57,7 +64,7 @@ const getIntegrationOptions = () => { image: 'Heroku.png', isAvailable: true, type: 'oauth', - clientId: infisical.get('CLIENT_ID_HEROKU')!, + clientId: getClientIdHeroku(), docsLink: '' }, { @@ -67,7 +74,7 @@ const getIntegrationOptions = () => { isAvailable: true, type: 'oauth', clientId: '', - clientSlug: infisical.get('CLIENT_SLUG_VERCEL')!, + clientSlug: getClientSlugVercel(), docsLink: '' }, { @@ -76,7 +83,7 @@ const getIntegrationOptions = () => { image: 'Netlify.png', isAvailable: true, type: 'oauth', - clientId: infisical.get('CLIENT_ID_NETLIFY')!, + clientId: getClientIdNetlify(), docsLink: '' }, { @@ -85,7 +92,7 @@ const getIntegrationOptions = () => { image: 'GitHub.png', isAvailable: true, type: 'oauth', - clientId: infisical.get('CLIENT_ID_GITHUB')!, + clientId: getClientIdGitHub(), docsLink: '' }, { @@ -130,7 +137,7 @@ const getIntegrationOptions = () => { image: 'Microsoft Azure.png', isAvailable: true, type: 'oauth', - clientId: infisical.get('CLIENT_ID_AZURE')!, + clientId: getClientIdAzure(), docsLink: '' }, { @@ -148,7 +155,7 @@ const getIntegrationOptions = () => { image: 'GitLab.png', isAvailable: true, type: 'custom', - clientId: infisical.get('CLIENT_ID_GITLAB'), + clientId: getClientIdGitLab(), docsLink: '' }, { diff --git a/docs/sdk/overview/usage.mdx b/docs/sdk/overview/usage.mdx index 2708f9a2b..84d963fff 100644 --- a/docs/sdk/overview/usage.mdx +++ b/docs/sdk/overview/usage.mdx @@ -59,17 +59,16 @@ infisical.connect({ Options: -| Option | Description | Default Value | -| -------------------- | ----------------------------------------------------------- | --------------------------- | -| `token` | ❗️ An Infisical Token to be used to fetch secrets | `None` | -| `siteURL` | Site URL of Infisical to connect to | `https://app.infisical.com` | -| `attachToProcessEnv` | Whether or not to attach fetched secrets to `process.env` | `False` | -| `defaultValues` | Default values for secrets if they aren't fetched/passed in | `{}` | +| Option | Description | Default Value | +| -------------------- | --------------------------------------------------------- | --------------------------- | +| `token` | ❗️ An Infisical Token to be used to fetch secrets | `None` | +| `siteURL` | Site URL of Infisical to connect to | `https://app.infisical.com` | +| `attachToProcessEnv` | Whether or not to attach fetched secrets to `process.env` | `false` | ## Access a Secret Value ```js -const dbURL = infisical.getSecretValue("DB_URL"); +const dbURL = infisical.get("DB_URL"); ``` ## Example with Express @@ -81,7 +80,7 @@ const infisical = require("infisical-node"); app.get("/", (req, res) => { // access value - const name = infisical.getSecret("NAME"); + const name = infisical.get("NAME"); res.send(`Hello! My name is: ${name}`); }); From 38efb6a1e2e56d4668f79ea7d6cb1493c1cd91bb Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Tue, 14 Mar 2023 22:07:52 -0700 Subject: [PATCH 04/35] Solved the issue with empty secret names --- frontend/src/pages/dashboard/[id].tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index 6c4483d49..9c485ebba 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -419,9 +419,20 @@ export default function Dashboard() { const nameErrors = !newData! .map((secret) => !Number.isNaN(Number(secret.key.charAt(0)))) .every((v) => v === false); + const emptyNameError = !newData! + .map((secret) => secret.key.length === 0) + .every((v) => v === false); const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0; + if (emptyNameError) { + setSaveLoading(false); + return createNotification({ + text: 'You can`t submit empty secret names.', + type: 'error' + }); + } + if (nameErrors) { setSaveLoading(false); return createNotification({ From dabc7e3eb1d0e1ac0cd10a9176a1ed8e8ce23526 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Tue, 14 Mar 2023 22:13:02 -0700 Subject: [PATCH 05/35] Solved the issue with empty secret names --- frontend/src/pages/dashboard/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index 9c485ebba..799c05c39 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -428,7 +428,7 @@ export default function Dashboard() { if (emptyNameError) { setSaveLoading(false); return createNotification({ - text: 'You can`t submit empty secret names.', + text: 'You can`t have empty secret names.', type: 'error' }); } From b868b6a5f33b4e97b7f6ba9d897adc136d36b8c9 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 15 Mar 2023 14:03:39 +0700 Subject: [PATCH 06/35] Clean up infisical-node --- backend/package-lock.json | 14 ++--- backend/package.json | 2 +- backend/src/config/index.ts | 5 +- .../src/controllers/v2/signupController.ts | 6 +- backend/src/index.ts | 7 +-- frontend/package-lock.json | 63 +++++++++++++++---- frontend/package.json | 3 +- 7 files changed, 70 insertions(+), 30 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 5dbf691ca..1eb1dbc66 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -32,7 +32,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", - "infisical-node": "^1.0.34", + "infisical-node": "^1.0.37", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", @@ -5975,9 +5975,9 @@ } }, "node_modules/infisical-node": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.34.tgz", - "integrity": "sha512-0joSgkNPZ15aZtm8Mrr/vSWizTYZlJivbawCecfllR4bzQ03TT3Ja4hivYyAmRYkrhUTHqb0gpQ3a8lSk7vyug==", + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.37.tgz", + "integrity": "sha512-9ZswN5UovZq46a7Qv/4KmfaAu9pO/TmxxdcEY2PosDIAlXbpfC651hcKr7T8q1iMlRnHLA/gpYkcZMeS0es0rg==", "dependencies": { "axios": "^1.3.3", "tweetnacl": "^1.0.3", @@ -16770,9 +16770,9 @@ "dev": true }, "infisical-node": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.34.tgz", - "integrity": "sha512-0joSgkNPZ15aZtm8Mrr/vSWizTYZlJivbawCecfllR4bzQ03TT3Ja4hivYyAmRYkrhUTHqb0gpQ3a8lSk7vyug==", + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.37.tgz", + "integrity": "sha512-9ZswN5UovZq46a7Qv/4KmfaAu9pO/TmxxdcEY2PosDIAlXbpfC651hcKr7T8q1iMlRnHLA/gpYkcZMeS0es0rg==", "requires": { "axios": "^1.3.3", "tweetnacl": "^1.0.3", diff --git a/backend/package.json b/backend/package.json index 04a5c0943..cd6cd214a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,7 +23,7 @@ "express-validator": "^6.14.2", "handlebars": "^4.7.7", "helmet": "^5.1.1", - "infisical-node": "^1.0.34", + "infisical-node": "^1.0.37", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index c4e072259..3fe935097 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,6 +1,6 @@ import infisical from 'infisical-node'; export const getPort = () => infisical.get('PORT')! || 4000; -export const getInviteOnlySignup = () => infisical.get('INVITE_ONLY_SIGNUP')! == undefined ? false : process.env.INVITE_ONLY_SIGNUP; +export const getInviteOnlySignup = () => infisical.get('INVITE_ONLY_SIGNUP')! == undefined ? false : infisical.get('INVITE_ONLY_SIGNUP'); export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!; export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10; export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d'; @@ -46,4 +46,5 @@ export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!; export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_KEY')!; export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!; export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!; -export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; \ No newline at end of file +export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; +export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; \ No newline at end of file diff --git a/backend/src/controllers/v2/signupController.ts b/backend/src/controllers/v2/signupController.ts index 3e4b1f44a..aaf055b78 100644 --- a/backend/src/controllers/v2/signupController.ts +++ b/backend/src/controllers/v2/signupController.ts @@ -8,7 +8,7 @@ import { import { issueAuthTokens } from '../../helpers/auth'; import { INVITED, ACCEPTED } from '../../variables'; import request from '../../config/request'; -import { getNodeEnv } from '../../config'; +import { getNodeEnv, getLoopsApiKey } from '../../config'; /** * Complete setting up user by adding their personal and auth information as part of the @@ -108,7 +108,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => { token = tokens.token; // sending a welcome email to new users - if (process.env.LOOPS_API_KEY) { + if (getLoopsApiKey()) { await request.post("https://app.loops.so/api/v1/events/send", { "email": email, "eventName": "Sign Up", @@ -117,7 +117,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => { }, { headers: { "Accept": "application/json", - "Authorization": "Bearer " + process.env.LOOPS_API_KEY + "Authorization": "Bearer " + getLoopsApiKey() }, }); } diff --git a/backend/src/index.ts b/backend/src/index.ts index 5647ad870..52740358d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -72,11 +72,8 @@ import { } from './config'; const main = async () => { - // TODO 1: handle case of empty string token - // TODO 2: handle case of undefined token - const client = await infisical.connect({ - token: process.env.INFISICAL_TOKEN!, - debug: true + await infisical.connect({ + token: process.env.INFISICAL_TOKEN! }); logTelemetryMessage(); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5c92b590b..ad099a70d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,5 +1,5 @@ { - "name": "npm-proj-1677883018530-0.7603125731052582NtcmfK", + "name": "frontend", "lockfileVersion": 2, "requires": true, "packages": { @@ -46,6 +46,7 @@ "gray-matter": "^4.0.3", "http-proxy": "^1.18.1", "i18next": "^22.4.9", + "infisical-node": "^1.0.37", "jspdf": "^2.5.1", "jsrp": "^0.2.4", "markdown-it": "^13.0.1", @@ -13367,6 +13368,26 @@ "node": ">=8" } }, + "node_modules/infisical-node": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.37.tgz", + "integrity": "sha512-9ZswN5UovZq46a7Qv/4KmfaAu9pO/TmxxdcEY2PosDIAlXbpfC651hcKr7T8q1iMlRnHLA/gpYkcZMeS0es0rg==", + "dependencies": { + "axios": "^1.3.3", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, + "node_modules/infisical-node/node_modules/axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -17363,8 +17384,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/pump": { "version": "3.0.0", @@ -21761,9 +21781,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", + "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -32078,6 +32098,28 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "infisical-node": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/infisical-node/-/infisical-node-1.0.37.tgz", + "integrity": "sha512-9ZswN5UovZq46a7Qv/4KmfaAu9pO/TmxxdcEY2PosDIAlXbpfC651hcKr7T8q1iMlRnHLA/gpYkcZMeS0es0rg==", + "requires": { + "axios": "^1.3.3", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + }, + "dependencies": { + "axios": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + } + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -34869,8 +34911,7 @@ "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "pump": { "version": "3.0.0", @@ -38113,9 +38154,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", + "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", diff --git a/frontend/package.json b/frontend/package.json index a1989701a..d53e40ca7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,8 +39,8 @@ "@reduxjs/toolkit": "^1.8.3", "@stripe/react-stripe-js": "^1.16.3", "@stripe/stripe-js": "^1.46.0", - "@types/argon2-browser": "^1.18.1", "@tanstack/react-query": "^4.23.0", + "@types/argon2-browser": "^1.18.1", "add": "^2.0.6", "argon2-browser": "^1.18.0", "axios": "^0.27.2", @@ -53,6 +53,7 @@ "gray-matter": "^4.0.3", "http-proxy": "^1.18.1", "i18next": "^22.4.9", + "infisical-node": "^1.0.37", "jspdf": "^2.5.1", "jsrp": "^0.2.4", "markdown-it": "^13.0.1", From db48ab8f6c81e5bc29b37aa4af4c636cb2ec5bfa Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 15 Mar 2023 15:16:50 +0700 Subject: [PATCH 07/35] Modify healthcheck.test --- backend/__tests__/healthcheck.test.ts | 11 ++++++++--- backend/src/index.ts | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/__tests__/healthcheck.test.ts b/backend/__tests__/healthcheck.test.ts index 234d2d8eb..945968437 100644 --- a/backend/__tests__/healthcheck.test.ts +++ b/backend/__tests__/healthcheck.test.ts @@ -1,11 +1,16 @@ -import { server } from '../src/app'; +import { Server } from 'http'; +import main from '../src'; import { describe, expect, it, beforeAll, afterAll } from '@jest/globals'; import supertest from 'supertest'; import { setUpHealthEndpoint } from '../src/services/health'; -const requestWithSupertest = supertest(server); +let requestWithSupertest: supertest.SuperTest; +let server: Server; + describe('Healthcheck endpoint', () => { beforeAll(async () => { + server = await main; + requestWithSupertest = supertest(server); setUpHealthEndpoint(server); }); afterAll(async () => { @@ -16,4 +21,4 @@ describe('Healthcheck endpoint', () => { const res = await requestWithSupertest.get('/healthcheck'); expect(res.status).toEqual(200); }); -}); +}); \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index 52740358d..90589404d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -169,6 +169,7 @@ const main = async () => { }); setUpHealthEndpoint(server); + return server; } -main(); \ No newline at end of file +export default main(); \ No newline at end of file From e6e3d82fa622a929d0e67be2ae7076620ae27662 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 15 Mar 2023 16:58:59 +0700 Subject: [PATCH 08/35] Modify healthcheck and server on-close to close database connection --- backend/__tests__/healthcheck.test.ts | 23 ++++++++++------------- backend/src/helpers/database.ts | 19 ++++++++++++++++++- backend/src/index.ts | 10 ++++++++-- backend/src/services/DatabaseService.ts | 18 +++++++++++++++++- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/backend/__tests__/healthcheck.test.ts b/backend/__tests__/healthcheck.test.ts index 945968437..e054bd180 100644 --- a/backend/__tests__/healthcheck.test.ts +++ b/backend/__tests__/healthcheck.test.ts @@ -1,24 +1,21 @@ import { Server } from 'http'; import main from '../src'; import { describe, expect, it, beforeAll, afterAll } from '@jest/globals'; -import supertest from 'supertest'; -import { setUpHealthEndpoint } from '../src/services/health'; +import request from 'supertest'; -let requestWithSupertest: supertest.SuperTest; let server: Server; -describe('Healthcheck endpoint', () => { - beforeAll(async () => { - server = await main; - requestWithSupertest = supertest(server); - setUpHealthEndpoint(server); - }); - afterAll(async () => { - server.close(); - }); +beforeAll(async () => { + server = await main; +}); +afterAll(async () => { + server.close(); +}); + +describe('Healthcheck endpoint', () => { it('GET /healthcheck should return OK', async () => { - const res = await requestWithSupertest.get('/healthcheck'); + const res = await request(server).get('/healthcheck'); expect(res.status).toEqual(200); }); }); \ No newline at end of file diff --git a/backend/src/helpers/database.ts b/backend/src/helpers/database.ts index 9ba592ea3..9d128ea47 100644 --- a/backend/src/helpers/database.ts +++ b/backend/src/helpers/database.ts @@ -29,6 +29,23 @@ const initDatabaseHelper = async ({ return mongoose.connection; } +/** + * Close database conection + */ +const closeDatabaseHelper = async () => { + return Promise.all([ + new Promise((resolve) => { + if (mongoose.connection && mongoose.connection.readyState == 1) { + mongoose.connection.close() + .then(() => resolve('Database connection closed')); + } else { + resolve('Database connection already closed'); + } + }) + ]); +} + export { - initDatabaseHelper + initDatabaseHelper, + closeDatabaseHelper } \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index 90589404d..64d58cfc5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,3 +1,4 @@ +import mongoose from 'mongoose'; import dotenv from 'dotenv'; dotenv.config(); import infisical from 'infisical-node'; @@ -158,17 +159,22 @@ const main = async () => { //* 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` })) + next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` })) }) app.use(requestErrorHandler) const server = app.listen(getPort(), () => { - createTestUserForDevelopment(); getLogger("backend-main").info(`Server started listening at port ${getPort()}`) }); + createTestUserForDevelopment(); setUpHealthEndpoint(server); + + server.on('close', async () => { + await DatabaseService.closeDatabase(); + }) + return server; } diff --git a/backend/src/services/DatabaseService.ts b/backend/src/services/DatabaseService.ts index 2e8dc839f..fdfd7660a 100644 --- a/backend/src/services/DatabaseService.ts +++ b/backend/src/services/DatabaseService.ts @@ -1,16 +1,32 @@ import mongoose from 'mongoose'; import { getLogger } from '../utils/logger'; -import { initDatabaseHelper } from '../helpers/database'; +import { + initDatabaseHelper, + closeDatabaseHelper +} from '../helpers/database'; /** * Class to handle database actions */ class DatabaseService { + /** + * Initialize database connection + * @param {Object} obj + * @param {String} obj.mongoURL - mongo connection string + * @returns + */ static async initDatabase(MONGO_URL: string) { return await initDatabaseHelper({ mongoURL: MONGO_URL }); } + + /** + * Close database conection + */ + static async closeDatabase() { + return await closeDatabaseHelper(); + } } export default DatabaseService; \ No newline at end of file From d7054404007b88a417837d288a72a52a59545b1f Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 15 Mar 2023 18:21:52 +0700 Subject: [PATCH 09/35] Revamp Node SDK docs --- docs/mint.json | 13 ++-- docs/sdk/overview/usage.mdx | 103 ------------------------------- docs/sdks/overview/go.mdx | 8 +++ docs/sdks/overview/java.mdx | 8 +++ docs/sdks/overview/node.mdx | 111 ++++++++++++++++++++++++++++++++++ docs/sdks/overview/python.mdx | 8 +++ docs/sdks/overview/ruby.mdx | 8 +++ docs/sdks/overview/rust.mdx | 8 +++ 8 files changed, 160 insertions(+), 107 deletions(-) delete mode 100644 docs/sdk/overview/usage.mdx create mode 100644 docs/sdks/overview/go.mdx create mode 100644 docs/sdks/overview/java.mdx create mode 100644 docs/sdks/overview/node.mdx create mode 100644 docs/sdks/overview/python.mdx create mode 100644 docs/sdks/overview/ruby.mdx create mode 100644 docs/sdks/overview/rust.mdx diff --git a/docs/mint.json b/docs/mint.json index 697a7bfeb..efd669781 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -50,9 +50,9 @@ "url": "self-hosting" }, { - "name": "SDK", + "name": "SDKs", "icon": "puzzle-piece", - "url": "sdk" + "url": "sdks" }, { "name": "API Reference", @@ -192,9 +192,14 @@ ] }, { - "group": "SDK", + "group": "SDKs", "pages": [ - "sdk/overview/usage" + "sdks/overview/node", + "sdks/overview/python", + "sdks/overview/java", + "sdks/overview/ruby", + "sdks/overview/go", + "sdks/overview/rust" ] }, { diff --git a/docs/sdk/overview/usage.mdx b/docs/sdk/overview/usage.mdx deleted file mode 100644 index 84d963fff..000000000 --- a/docs/sdk/overview/usage.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: "Usage" ---- - - - We're currently expanding the functionality of the Javascript SDK and working - on mirror SDKs for other languages like Python as well. Follow this GitHub - [issue](https://github.com/Infisical/infisical/issues/320) to stay updated. - - -Infisical provides a [Node SDK](https://github.com/Infisical/infisical-node) that users can easily install into their applications and use to fetch their secrets. - -With the SDK, users can currently fetch back secrets and define default values. - - - - -## Installation - -```bash -$ npm install infisical-node -``` - -## Import - -```js -// ES6 syntax -import infisical from "infisical-node"; - -// ES5 syntax -const infisical = require("infisical-node"); -``` - -## Initialization - -If your app only needs to connect to one Infisical project, you should use `infisical.connect`. If you need to connect to multiple Infisical projects, use `infisical.createConnection`. - -Both `connect` and `createConnection` take a parameter `token` and pull in the secrets accessible by that Infisical token. - -```js -// using async-await (recommended) -await infisical.connect({ - token: "your_infisical_token", -}); -``` - -```js -// using promise chaining -infisical.connect({ - token: "your_infisical_token" -}) -.then(() => { - console.log('Success!) -}) -.catch(err => { - console.error('Error: ', err); -}) -``` - -Options: - -| Option | Description | Default Value | -| -------------------- | --------------------------------------------------------- | --------------------------- | -| `token` | ❗️ An Infisical Token to be used to fetch secrets | `None` | -| `siteURL` | Site URL of Infisical to connect to | `https://app.infisical.com` | -| `attachToProcessEnv` | Whether or not to attach fetched secrets to `process.env` | `false` | - -## Access a Secret Value - -```js -const dbURL = infisical.get("DB_URL"); -``` - -## Example with Express - -```js -const express = require("express"); -const port = 3000; -const infisical = require("infisical-node"); - -app.get("/", (req, res) => { - // access value - const name = infisical.get("NAME"); - - res.send(`Hello! My name is: ${name}`); -}); - -app.listen(port, async () => { - // initialize client - await infisical.connect({ - token: "YOUR_INFISICAL_TOKEN", - }); - - console.log(`App listening on port ${port}`); -}); -``` - - - - Coming soon. - - - diff --git a/docs/sdks/overview/go.mdx b/docs/sdks/overview/go.mdx new file mode 100644 index 000000000..0ff2a2dde --- /dev/null +++ b/docs/sdks/overview/go.mdx @@ -0,0 +1,8 @@ +--- +title: "Go" +--- + +Coming soon. + +Follow this GitHub +[issue](https://github.com/Infisical/infisical/issues/436) to stay updated. diff --git a/docs/sdks/overview/java.mdx b/docs/sdks/overview/java.mdx new file mode 100644 index 000000000..2dd2c3c1d --- /dev/null +++ b/docs/sdks/overview/java.mdx @@ -0,0 +1,8 @@ +--- +title: "Java" +--- + +Coming soon. + +Follow this GitHub +[issue](https://github.com/Infisical/infisical/issues/434) to stay updated. diff --git a/docs/sdks/overview/node.mdx b/docs/sdks/overview/node.mdx new file mode 100644 index 000000000..37928933f --- /dev/null +++ b/docs/sdks/overview/node.mdx @@ -0,0 +1,111 @@ +--- +title: "Node" +--- + +If you're working with Node.js, the official [infisical-node](https://github.com/Infisical/infisical-node) package is the easiest way to fetch secrets for your application. + +## Installation + +Run `npm` to add `infisical` to your project. + +```bash +npm install infisical-node --save +``` + +## Initialization + +Set up `infisical` asynchronously as early as possible in your application by importing and initializing the global instance with `infisical.connect([options])`. + +This methods fetches back all the secrets in the project and environment accessible by the token passed in `options`. + + + + ```js + import infisical from "infisical-node"; + + const main = async () => { + await infisical.connect({ + token: "your_infisical_token", + }); + + // your app logic + } + + main(); + ``` + + + + ```js + const infisical = require("infisical-node"); + + infisical.connect({ + token: "your_infisical_token" + }) + .then(() => { + // your application logic + }) + .catch(err => { + console.error('Error: ', err); + }) + ```` + + + + +`infisical.connect([options])` + +| Option | Description | Default Value | +| --------- | -------------------------------------------------------------------------------------------- | --------------------------- | +| `token` | An [Infisical Token](/getting-started/dashboard/token) scoped to a project and environment | `None` | +| `siteURL` | Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`) | `https://app.infisical.com` | +| `debug` | Whether or not debug mode is on | `false` | + + + If you need to connect to multiple Infisical projects, you can use + `infisical.createConnection([options])` to return a local instance of + `infisical`. + + +## Usage + +To get the value of secret, pass the name of its key into `infisical.get()`. + +```js +const value = infisical.get("SOME_KEY"); +``` + + + `infisical` falls back to `process.env` if `token` is `undefined` during + initialization or if a value is not found in the secrets fetched. + + +## Example with Express + +```js +const express = require("express"); +const port = 3000; +const infisical = require("infisical-node"); + +const main = async () => { + await infisical.connect({ + token: "st.xxx.xxx", + }); + + // your application logic + + app.get("/", (req, res) => { + res.send(`Howdy, ${infisical.get("NAME")}!`); + }); + + app.listen(port, async () => { + console.log(`App listening on port ${port}`); + }); +}; +``` + + + We do not recommend hardcoding your [Infisical + Token](/getting-started/dashboard/token). Setting it as an environment + variable would be best. + diff --git a/docs/sdks/overview/python.mdx b/docs/sdks/overview/python.mdx new file mode 100644 index 000000000..3a9b82152 --- /dev/null +++ b/docs/sdks/overview/python.mdx @@ -0,0 +1,8 @@ +--- +title: "Python" +--- + +Coming soon. + +Follow this GitHub +[issue](https://github.com/Infisical/infisical/issues/433) to stay updated. diff --git a/docs/sdks/overview/ruby.mdx b/docs/sdks/overview/ruby.mdx new file mode 100644 index 000000000..80dab508a --- /dev/null +++ b/docs/sdks/overview/ruby.mdx @@ -0,0 +1,8 @@ +--- +title: "Ruby" +--- + +Coming soon. + +Follow this GitHub +[issue](https://github.com/Infisical/infisical/issues/435) to stay updated. diff --git a/docs/sdks/overview/rust.mdx b/docs/sdks/overview/rust.mdx new file mode 100644 index 000000000..8fa9b3b5b --- /dev/null +++ b/docs/sdks/overview/rust.mdx @@ -0,0 +1,8 @@ +--- +title: "Rust" +--- + +Coming soon. + +Follow this GitHub +[issue](https://github.com/Infisical/infisical/issues/437) to stay updated. From 778631f3965d13c436225c385165e7998f043d1a Mon Sep 17 00:00:00 2001 From: Rachid Zarouali Date: Wed, 15 Mar 2023 18:14:09 +0100 Subject: [PATCH 10/35] typo fix removed the 'add' from the helm uninstall command --- docs/integrations/platforms/kubernetes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 069fe1ed4..959a4fce2 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -362,7 +362,7 @@ The managed secret created by the operator will not be deleted when the operator Install Infisical Helm repository ```bash - helm uninstall add + helm uninstall ``` From b591d638d0e61d1f8dc9ba9434cc47972e0a45e9 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 16 Mar 2023 00:28:29 +0700 Subject: [PATCH 11/35] Update Node SDK docs --- docs/sdks/overview/node.mdx | 87 +++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/docs/sdks/overview/node.mdx b/docs/sdks/overview/node.mdx index 37928933f..58002392f 100644 --- a/docs/sdks/overview/node.mdx +++ b/docs/sdks/overview/node.mdx @@ -6,7 +6,7 @@ If you're working with Node.js, the official [infisical-node](https://github.com ## Installation -Run `npm` to add `infisical` to your project. +Run `npm` to add `infisical-node` to your project. ```bash npm install infisical-node --save @@ -14,10 +14,63 @@ npm install infisical-node --save ## Initialization -Set up `infisical` asynchronously as early as possible in your application by importing and initializing the global instance with `infisical.connect([options])`. +Set up the Infisical client asynchronously as early as possible in your application by importing and initializing the global instance with `infisical.connect(options)`. This methods fetches back all the secrets in the project and environment accessible by the token passed in `options`. +### infisical.connect(options) + +Updates the global instance of the Infisical client with a connection to an Infisical project and fetches back secrets if supplied with an [Infisical Token](/getting-started/dashboard/token). + + + + + An [Infisical Token](/getting-started/dashboard/token) scoped to a project + and environment + + + Your self-hosted absolute site URL including the protocol (e.g. + `https://app.infisical.com`) + + + Whether or not debug mode is on + + + Whether or not to attach fetched secrets to `process.env` + + + + +### infisical.createConnection(options) + +Returns a local instance of the Infisical client with a connection to an Infisical project and fetches back secrets if supplied with an [Infisical Token](/getting-started/dashboard/token). + +This method is useful if you wish to connect to two or more Infisical projects within your app. + + + + + An [Infisical Token](/getting-started/dashboard/token) scoped to a project + and environment + + + Your self-hosted absolute site URL including the protocol (e.g. + `https://app.infisical.com`) + + + Whether or not debug mode is on + + + + ```js @@ -53,33 +106,23 @@ This methods fetches back all the secrets in the project and environment accessi -`infisical.connect([options])` - -| Option | Description | Default Value | -| --------- | -------------------------------------------------------------------------------------------- | --------------------------- | -| `token` | An [Infisical Token](/getting-started/dashboard/token) scoped to a project and environment | `None` | -| `siteURL` | Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`) | `https://app.infisical.com` | -| `debug` | Whether or not debug mode is on | `false` | - - - If you need to connect to multiple Infisical projects, you can use - `infisical.createConnection([options])` to return a local instance of - `infisical`. - - ## Usage -To get the value of secret, pass the name of its key into `infisical.get()`. +To get the value of a secret, use `infisical.get(key)`. + +### infisical.get(key) + +Return the value of the secret with the specified `key`. Note that the Infisical client falls back to `process.env` if `token` is `undefined` during the +initialization step or if a value for the secret is not found in the fetched secrets. + + + The key of the secret + ```js const value = infisical.get("SOME_KEY"); ``` - - `infisical` falls back to `process.env` if `token` is `undefined` during - initialization or if a value is not found in the secrets fetched. - - ## Example with Express ```js From 3ac98ba326ee01414dafe6acd52c29ae266c886d Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 16 Mar 2023 15:47:24 +0700 Subject: [PATCH 12/35] Improve SDK, quickstart, token, features docs --- docker-compose.dev.yml | 5 +- docs/getting-started/dashboard/token.mdx | 19 ++-- docs/getting-started/features.mdx | 28 ++--- docs/getting-started/introduction.mdx | 16 ++- docs/getting-started/quickstart.mdx | 112 +++++++++++++++---- docs/mint.json | 18 ++- docs/sdks/{overview => languages}/go.mdx | 0 docs/sdks/{overview => languages}/java.mdx | 0 docs/sdks/{overview => languages}/node.mdx | 0 docs/sdks/{overview => languages}/python.mdx | 0 docs/sdks/{overview => languages}/ruby.mdx | 0 docs/sdks/{overview => languages}/rust.mdx | 0 docs/sdks/overview.mdx | 18 +++ 13 files changed, 150 insertions(+), 66 deletions(-) rename docs/sdks/{overview => languages}/go.mdx (100%) rename docs/sdks/{overview => languages}/java.mdx (100%) rename docs/sdks/{overview => languages}/node.mdx (100%) rename docs/sdks/{overview => languages}/python.mdx (100%) rename docs/sdks/{overview => languages}/ruby.mdx (100%) rename docs/sdks/{overview => languages}/rust.mdx (100%) create mode 100644 docs/sdks/overview.mdx diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 007884694..7e904cd14 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -34,6 +34,7 @@ services: env_file: .env environment: - NODE_ENV=development + - MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin networks: - infisical-dev @@ -65,8 +66,8 @@ services: restart: always env_file: .env environment: - - MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME} - - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=example volumes: - mongo-data:/data/db networks: diff --git a/docs/getting-started/dashboard/token.mdx b/docs/getting-started/dashboard/token.mdx index 0caaa81d7..caeb1641d 100644 --- a/docs/getting-started/dashboard/token.mdx +++ b/docs/getting-started/dashboard/token.mdx @@ -1,26 +1,21 @@ --- title: "Infisical Token" -description: "Use Infisical service token as one of the authentication methods." +description: "Use the Infisical Token as one of the authentication methods." --- -An Infisical Token is needed to authenticate the CLI when there isn't an easy way to input your login credentials. +An Infisical Token is useful for: -It's useful for your CI/CD environments and integrations such as [Docker](/integrations/platforms/docker) and [Docker Compose](/integrations/platforms/docker-compose). +- Authenticating the [Infisical CLI](/cli/overview) when there isn't an easy way to input your login credentials. +- Granting the [Infisical SDKs](/sdks/overview) access to secrets scoped to a project and environment. + +It's also useful for CI/CD environments and integrations such as [Docker](/integrations/platforms/docker) and [Docker Compose](/integrations/platforms/docker-compose). To generate the the token, head over to your project settings as shown below. - ![token add](../../images/project-token-add.png) ## Feeding Infisical Token to the CLI -The Infisical CLI checks for the presence of an environment variable called `INFISICAL_TOKEN`. +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. - - - 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 - requests with it. - - diff --git a/docs/getting-started/features.mdx b/docs/getting-started/features.mdx index 5b163806b..9eb1d4faf 100644 --- a/docs/getting-started/features.mdx +++ b/docs/getting-started/features.mdx @@ -1,10 +1,8 @@ --- title: "Features" -description: "A list of features that Infisical has to offer." +description: "A non-exhaustive list of features that Infisical has to offer." --- -This is a non-exhaustive list of features that Infisical offers: - ## Platform - Provision members access to organizations and projects. @@ -16,29 +14,17 @@ This is a non-exhaustive list of features that Infisical offers: ## CLI -The CLI is used to inject environment variables into applications and infrastructure. +The [CLI](/cli/overview) is used to inject environment variables into applications and infrastructure. - Inject environment variables. - Inject environment variables into containers via service tokens for Docker. +## SDKs + +[SDKs](/sdks/overview) enable apps to fetch back secrets using an [Infisical Token](/getting-started/dashboard/token) scoped to a project and environment. + ## Roadmap -We're building the future of secret management, one that's comprehensive and accessible to all. Some high-level features we have in mind: - -| Feature | Status | -| ------------------------------------- | ----------- | -| Account recovery: Backup key | Done | -| 2FA | Done | -| Read/write access controls | Done | -| Comparing secrets across environments | Done | -| Integrations | Ongoing | -| More hosting options | Ongoing | -| 1-Click Deploys | Ongoing | -| Access logs | Ongoing | -| Account recovery: Member-assisted | Coming soon | -| Slack & MS teams integrations | Coming soon | -| Version control for secrets | Coming soon | -| Restricted IPs | Coming soon | -| Secret rotation | Coming soon | +We're building the future of secret management, one that's comprehensive and accessible to all. Check out our [roadmap](https://www.notion.so/infisical/be2d2585a6694e40889b03aef96ea36b?v=5b19a8127d1a4060b54769567a8785fa). Interested in contributing? Check out the [guide](/contributing/overview). diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx index ff8b72c4d..38dcffe3a 100644 --- a/docs/getting-started/introduction.mdx +++ b/docs/getting-started/introduction.mdx @@ -19,6 +19,9 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical Install the CLI to inject secrets into apps and infra. + + Install an SDK into your app to fetch secrets. + - - - Our team is happy to help you get started with Infisical. If you have any questions or want to learn how you can leverage Infisical within your infrastructure, **[set up a 1-on-1 with an Infisical engineer](https://cal.com/maidul/15min)**. + + Our team is happy to help you get started with Infisical. If you have any + questions or want to learn how you can leverage Infisical within your + infrastructure, **[set up a 1-on-1 with an Infisical + engineer](https://cal.com/maidul/15min)**. diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx index e3acd440d..256ae8f85 100644 --- a/docs/getting-started/quickstart.mdx +++ b/docs/getting-started/quickstart.mdx @@ -3,9 +3,7 @@ title: "Quickstart" description: "Start managing your developer secrets and configs with Infisical in 10 minutes." --- -This example demonstrates how to store and inject environment variables from [Infisical Cloud](https://app.infisical.com) into your application. - -Note that the Infisical CLI is platform-agnostic and can inject environment variables across many tech stacks and frameworks. +These examples demonstrate how to store and fetch environment variables from [Infisical Cloud](https://app.infisical.com) into your application. ## Set up Infisical Cloud @@ -15,30 +13,100 @@ Note that the Infisical CLI is platform-agnostic and can inject environment vari ![project quickstart](../images/project-quickstart.png) -## Set up the CLI +## Fetch Secrets for Your App -1. Follow the instructions to [install the CLI](/cli/overview). + + + + The Infisical CLI is platform-agnostic and enables you to inject environment variables into your app across many tech stacks and frameworks. -2. Initialize Infisical for your project. + ### Set up the CLI -```bash -# move to your project -cd /path/to/project + 1. Follow the instructions to [install our platform-agnostic CLI](/cli/overview). -# initialize infisical -infisical init -``` + 2. Initialize Infisical for your project. -## Start your app with environment variables injected + ```bash + # move to your project + cd /path/to/project -```bash -# inject environment variables into app -infisical run -- [your application start command] -``` + # initialize infisical + infisical init + ``` - - Check out our [integrations](/integrations/overview) for injecting environment - variables into frameworks and platforms like Docker. - + ### Start your app with environment variables injected -Your app should be running with the environment variables injected. + ```bash + # inject environment variables into app + infisical run -- [your application start command] + ``` + + Your app should now be running with the environment variables injected. + + Check out our [integrations](/integrations/overview) for injecting environment variables into frameworks and platforms like Docker. + + + + [Infisical SDKs](/sdks/overview) let your app fetch back secrets using an [Infisical Token](/getting-started/dashboard/token) that is scoped to a project and environment in Infisical. In this example, we demonstrate how to use the [Node SDK](/sdks/languages/node). + + ### Obtain an [Infisical Token](/getting-started/dashboard/token) + + Head to your project settings to create a token scoped to the project and environment you wish to fetch secrets from. + + ![token add](../images/project-token-add.png) + + ### Install the SDK + + ```bash + npm install infisical-node --save + ``` + + ### Initialize the Infisical client + + ```js + await infisical.connect({ + token: "your_infisical_token", + }); + ``` + + ### Get a value + + ```js + const value = infisical.get("SOME_KEY"); + ``` + + ### Example with Express + + ```js + const express = require("express"); + const port = 3000; + const infisical = require("infisical-node"); + + const main = async () => { + await infisical.connect({ + token: "st.xxx.xxx", + }); + + // your application logic + + app.get("/", (req, res) => { + res.send(`Howdy, ${infisical.get("NAME")}!`); + }); + + app.listen(port, async () => { + console.log(`App listening on port ${port}`); + }); + }; + ``` + + + We do not recommend hardcoding your [Infisical + Token](/getting-started/dashboard/token). Setting it as an environment + variable would be best. + + + + Check out our [SDKs](/sdks/overview) for other language SDKs. + + + diff --git a/docs/mint.json b/docs/mint.json index efd669781..e257ea0c5 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -191,15 +191,21 @@ "self-hosting/configuration/email" ] }, + { + "group": "Overview", + "pages": [ + "sdks/overview" + ] + }, { "group": "SDKs", "pages": [ - "sdks/overview/node", - "sdks/overview/python", - "sdks/overview/java", - "sdks/overview/ruby", - "sdks/overview/go", - "sdks/overview/rust" + "sdks/languages/node", + "sdks/languages/python", + "sdks/languages/java", + "sdks/languages/ruby", + "sdks/languages/go", + "sdks/languages/rust" ] }, { diff --git a/docs/sdks/overview/go.mdx b/docs/sdks/languages/go.mdx similarity index 100% rename from docs/sdks/overview/go.mdx rename to docs/sdks/languages/go.mdx diff --git a/docs/sdks/overview/java.mdx b/docs/sdks/languages/java.mdx similarity index 100% rename from docs/sdks/overview/java.mdx rename to docs/sdks/languages/java.mdx diff --git a/docs/sdks/overview/node.mdx b/docs/sdks/languages/node.mdx similarity index 100% rename from docs/sdks/overview/node.mdx rename to docs/sdks/languages/node.mdx diff --git a/docs/sdks/overview/python.mdx b/docs/sdks/languages/python.mdx similarity index 100% rename from docs/sdks/overview/python.mdx rename to docs/sdks/languages/python.mdx diff --git a/docs/sdks/overview/ruby.mdx b/docs/sdks/languages/ruby.mdx similarity index 100% rename from docs/sdks/overview/ruby.mdx rename to docs/sdks/languages/ruby.mdx diff --git a/docs/sdks/overview/rust.mdx b/docs/sdks/languages/rust.mdx similarity index 100% rename from docs/sdks/overview/rust.mdx rename to docs/sdks/languages/rust.mdx diff --git a/docs/sdks/overview.mdx b/docs/sdks/overview.mdx new file mode 100644 index 000000000..fd27ad902 --- /dev/null +++ b/docs/sdks/overview.mdx @@ -0,0 +1,18 @@ +--- +title: "Overview" +description: "How to use Infisical SDKs to fetch back secrets for your app" +--- + +Infisical SDKs provide the easiest way for your app to fetch back secrets using an [Infisical Token](/getting-started/dashboard/token) and has a few benefits: + +- Local development: Replace 10s of environment variables in your `.env` file with 1 environment variable (the [Infisical Token](/getting-started/dashboard/token)). +- Production: Fetch secrets back to any cloud regardless of if an integration exists between Infisical and the cloud platform. + +We currently only have the [Node SDK](/sdks/languages/node) available but more language SDKs are coming out soon: + +- [Node](/sdks/languages/node) +- [Python](/sdks/languages/python) +- [Java](/sdks/languages/java) +- [Ruby](/sdks/languages/ruby) +- [Go](/sdks/languages/go) +- [Rust](/sdks/languages/rust) From 3d818f953d5bfaa3b26742663c57c9355537615b Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 16 Mar 2023 16:44:47 +0700 Subject: [PATCH 13/35] Make root/example default DB user in development --- docker-compose.dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7e904cd14..287e80945 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -81,9 +81,9 @@ services: - mongo env_file: .env environment: - - ME_CONFIG_MONGODB_ADMINUSERNAME=${MONGO_USERNAME} - - ME_CONFIG_MONGODB_ADMINPASSWORD=${MONGO_PASSWORD} - - ME_CONFIG_MONGODB_URL=mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@mongo:27017/ + - ME_CONFIG_MONGODB_ADMINUSERNAME=root + - ME_CONFIG_MONGODB_ADMINPASSWORD=example + - ME_CONFIG_MONGODB_URL=mongodb://root:example@mongo:27017/ ports: - 8081:8081 networks: From 46f2b7a3f8e7fc261e3a7b8174f77a41b33b78cd Mon Sep 17 00:00:00 2001 From: Aashish-Upadhyay-101 Date: Thu, 16 Mar 2023 17:34:11 +0545 Subject: [PATCH 14/35] test setup --- backend/jest.config.ts | 9 +++++++++ backend/package.json | 11 ----------- backend/setupTests.ts | 11 +++++++++++ backend/{__tests__ => tests}/healthcheck.test.ts | 0 4 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 backend/jest.config.ts create mode 100644 backend/setupTests.ts rename backend/{__tests__ => tests}/healthcheck.test.ts (100%) diff --git a/backend/jest.config.ts b/backend/jest.config.ts new file mode 100644 index 000000000..b7ca988db --- /dev/null +++ b/backend/jest.config.ts @@ -0,0 +1,9 @@ +export default { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverageFrom: ['src/*.{js,ts}', '!**/node_modules/**'], + modulePaths: ['/src'], + testMatch: ['/tests/**/*.test.ts'], + setupFiles: ['/test-resources/env-vars.js'], + setupFilesAfterEnv: ['/setupTests.ts'] +}; diff --git a/backend/package.json b/backend/package.json index cd6cd214a..f24ef80fa 100644 --- a/backend/package.json +++ b/backend/package.json @@ -101,17 +101,6 @@ "ts-jest": "^29.0.3", "ts-node": "^10.9.1" }, - "jest": { - "preset": "ts-jest", - "testEnvironment": "node", - "collectCoverageFrom": [ - "src/*.{js,ts}", - "!**/node_modules/**" - ], - "setupFiles": [ - "/test-resources/env-vars.js" - ] - }, "jest-junit": { "outputDirectory": "reports", "outputName": "jest-junit.xml", diff --git a/backend/setupTests.ts b/backend/setupTests.ts new file mode 100644 index 000000000..c98a366f9 --- /dev/null +++ b/backend/setupTests.ts @@ -0,0 +1,11 @@ +import { beforeAll, afterAll } from '@jest/globals'; +import { DatabaseService } from './src/services'; + +beforeAll(async () => { + const mongoURL = process.env.MONGO_URL!; + await DatabaseService.initDatabase(mongoURL); +}); + +afterAll(async () => { + await DatabaseService.closeDatabase(); +}); diff --git a/backend/__tests__/healthcheck.test.ts b/backend/tests/healthcheck.test.ts similarity index 100% rename from backend/__tests__/healthcheck.test.ts rename to backend/tests/healthcheck.test.ts From ded5c50157463af0ba2a562f95c4345d6237a358 Mon Sep 17 00:00:00 2001 From: Aashish-Upadhyay-101 Date: Thu, 16 Mar 2023 17:36:49 +0545 Subject: [PATCH 15/35] setup done ! --- backend/tests/healthcheck.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/healthcheck.test.ts b/backend/tests/healthcheck.test.ts index e054bd180..903347a2a 100644 --- a/backend/tests/healthcheck.test.ts +++ b/backend/tests/healthcheck.test.ts @@ -18,4 +18,4 @@ describe('Healthcheck endpoint', () => { const res = await request(server).get('/healthcheck'); expect(res.status).toEqual(200); }); -}); \ No newline at end of file +}); From 8e55d17a5501b0ad4ce076e80a1d72f5d4bb3f05 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 16 Mar 2023 09:56:42 -0700 Subject: [PATCH 16/35] show popup when email not configured --- .../src/components/signup/TeamInviteStep.tsx | 19 +++++++++++++- .../EmailServiceSetupModal.tsx | 21 ++++++++++++++++ .../v2/EmailServiceSetupModal/index.tsx | 1 + frontend/src/components/v2/index.tsx | 1 + frontend/src/hooks/api/serverDetails/index.ts | 1 + .../src/hooks/api/serverDetails/queries.tsx | 19 ++++++++++++++ frontend/src/hooks/api/serverDetails/types.ts | 5 ++++ frontend/src/pages/signup.tsx | 12 ++++++++- frontend/src/pages/verify-email.tsx | 20 ++++++++++++++- .../OrgIncidentContactsTable.tsx | 18 +++++++++++-- .../OrgMembersTable/OrgMembersTable.tsx | 25 +++++++++++++------ .../SecuritySection/SecuritySection.tsx | 21 ++++++++++++++-- 12 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/v2/EmailServiceSetupModal/EmailServiceSetupModal.tsx create mode 100644 frontend/src/components/v2/EmailServiceSetupModal/index.tsx create mode 100644 frontend/src/hooks/api/serverDetails/index.ts create mode 100644 frontend/src/hooks/api/serverDetails/queries.tsx create mode 100644 frontend/src/hooks/api/serverDetails/types.ts diff --git a/frontend/src/components/signup/TeamInviteStep.tsx b/frontend/src/components/signup/TeamInviteStep.tsx index 29cb95076..12d558606 100644 --- a/frontend/src/components/signup/TeamInviteStep.tsx +++ b/frontend/src/components/signup/TeamInviteStep.tsx @@ -2,10 +2,13 @@ import React, { useState } from 'react'; import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; +import { usePopUp } from '@app/hooks/usePopUp'; import addUserToOrg from '@app/pages/api/organization/addUserToOrg'; import getWorkspaces from '@app/pages/api/workspace/getWorkspaces'; import Button from '../basic/buttons/Button'; +import { EmailServiceSetupModal } from '../v2'; /** * This is the last step of the signup flow. People can optionally invite their teammates here. @@ -14,6 +17,10 @@ export default function TeamInviteStep(): JSX.Element { const [emails, setEmails] = useState(''); const { t } = useTranslation(); const router = useRouter(); + const {data: serverDetails } = useFetchServerStatus() + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([ + 'setUpEmail' + ] as const); // Redirect user to the getting started page const redirectToHome = async () => { @@ -62,10 +69,20 @@ export default function TeamInviteStep(): JSX.Element { + + + +); diff --git a/frontend/src/components/v2/EmailServiceSetupModal/index.tsx b/frontend/src/components/v2/EmailServiceSetupModal/index.tsx new file mode 100644 index 000000000..33a7d1854 --- /dev/null +++ b/frontend/src/components/v2/EmailServiceSetupModal/index.tsx @@ -0,0 +1 @@ +export { EmailServiceSetupModal } from './EmailServiceSetupModal'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 52c8fc4f4..130b797ae 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -3,6 +3,7 @@ export * from './Card'; export * from './Checkbox'; export * from './DeleteActionModal'; export * from './Dropdown'; +export * from './EmailServiceSetupModal' export * from './EmptyState'; export * from './FormControl'; export * from './IconButton'; diff --git a/frontend/src/hooks/api/serverDetails/index.ts b/frontend/src/hooks/api/serverDetails/index.ts new file mode 100644 index 000000000..812293a1d --- /dev/null +++ b/frontend/src/hooks/api/serverDetails/index.ts @@ -0,0 +1 @@ +export { useFetchServerStatus } from './queries' \ No newline at end of file diff --git a/frontend/src/hooks/api/serverDetails/queries.tsx b/frontend/src/hooks/api/serverDetails/queries.tsx new file mode 100644 index 000000000..29abea127 --- /dev/null +++ b/frontend/src/hooks/api/serverDetails/queries.tsx @@ -0,0 +1,19 @@ +import {useQuery } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { ServerStatus } from './types'; + +// cache key +const serverStatusKeys = { + serverStatus: ['serverStatus'] as const +}; + +const fetchServerStatus = async () => { + const {data} = await apiRequest.get('/api/status'); + return data; +}; + +export const useFetchServerStatus= () => { + return useQuery({ queryKey: serverStatusKeys.serverStatus, queryFn: fetchServerStatus }); +} \ No newline at end of file diff --git a/frontend/src/hooks/api/serverDetails/types.ts b/frontend/src/hooks/api/serverDetails/types.ts new file mode 100644 index 000000000..0deb90a38 --- /dev/null +++ b/frontend/src/hooks/api/serverDetails/types.ts @@ -0,0 +1,5 @@ +export type ServerStatus = { + date: string; + message: string; + emailConfigured: boolean; +}; \ No newline at end of file diff --git a/frontend/src/pages/signup.tsx b/frontend/src/pages/signup.tsx index 62d0f4a47..298fc9536 100644 --- a/frontend/src/pages/signup.tsx +++ b/frontend/src/pages/signup.tsx @@ -13,6 +13,7 @@ import TeamInviteStep from '@app/components/signup/TeamInviteStep'; import UserInfoStep from '@app/components/signup/UserInfoStep'; import SecurityClient from '@app/components/utilities/SecurityClient'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode'; import getWorkspaces from './api/workspace/getWorkspaces'; @@ -25,10 +26,12 @@ export default function SignUp() { const [password, setPassword] = useState(''); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); - const [code, setCode] = useState(''); + const [code, setCode] = useState('123456'); const [codeError, setCodeError] = useState(false); const [step, setStep] = useState(1); const router = useRouter(); + const {data: serverDetails } = useFetchServerStatus() + const { t } = useTranslation(); @@ -68,6 +71,13 @@ export default function SignUp() { } }; + // when email service is not configured, skip step 2 + useEffect(() => { + if (!serverDetails?.emailConfigured && step === 2){ + incrementStep() + } + }, [step]); + return (
diff --git a/frontend/src/pages/verify-email.tsx b/frontend/src/pages/verify-email.tsx index 3b7e1c8d5..203b040bf 100644 --- a/frontend/src/pages/verify-email.tsx +++ b/frontend/src/pages/verify-email.tsx @@ -6,12 +6,19 @@ import Link from 'next/link'; import Button from '@app/components/basic/buttons/Button'; import InputField from '@app/components/basic/InputField'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; +import { EmailServiceSetupModal } from '@app/components/v2'; +import { usePopUp } from '@app/hooks'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset'; export default function VerifyEmail() { const [email, setEmail] = useState(''); const [step, setStep] = useState(1); + const {data: serverDetails } = useFetchServerStatus() + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([ + 'setUpEmail' + ] as const); /** * This function sends the verification email and forwards a user to the next step. @@ -63,7 +70,13 @@ export default function VerifyEmail() {
-
@@ -80,6 +93,11 @@ export default function VerifyEmail() { )} + + handlePopUpToggle('setUpEmail', isOpen)} + /> ); } diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx index c3bd55f51..e118e455b 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx @@ -13,6 +13,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, + EmailServiceSetupModal, EmptyState, FormControl, IconButton, @@ -28,6 +29,7 @@ import { THead, Tr} from '@app/components/v2'; import { usePopUp } from '@app/hooks'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import { IncidentContact } from '@app/hooks/api/types'; type Props = { @@ -50,9 +52,11 @@ export const OrgIncidentContactsTable = ({ isLoading }: Props) => { const [searchContact, setSearchContact] = useState(''); + const {data: serverDetails } = useFetchServerStatus() const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ 'addContact', - 'removeContact' + 'removeContact', + 'setUpEmail' ] as const); const { @@ -92,7 +96,13 @@ export const OrgIncidentContactsTable = ({
@@ -180,6 +190,10 @@ export const OrgIncidentContactsTable = ({ onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)} onDeleteApproved={onRemoveIncidentContact} /> + handlePopUpToggle('setUpEmail', isOpen)} + />
); }; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx index 9bed5954b..b127abeb9 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx @@ -9,7 +9,7 @@ import * as yup from 'yup'; import { Button, DeleteActionModal, - EmptyState, + EmailServiceSetupModal, EmptyState, FormControl, IconButton, Input, @@ -28,6 +28,7 @@ import { Tr, UpgradePlanModal} from '@app/components/v2'; import { usePopUp } from '@app/hooks'; +import { useFetchServerStatus } from '@app/hooks/api/serverDetails'; import { OrgUser, Workspace } from '@app/hooks/api/types'; type Props = { @@ -64,10 +65,12 @@ export const OrgMembersTable = ({ }: Props) => { const router = useRouter(); const [searchMemberFilter, setSearchMemberFilter] = useState(''); + const {data: serverDetails } = useFetchServerStatus() const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ 'addMember', 'removeMember', - 'upgradePlan' + 'upgradePlan', + 'setUpEmail' ] as const); const { @@ -79,8 +82,8 @@ export const OrgMembersTable = ({ const onAddMember = async ({ email }: TAddMemberForm) => { await onInviteMember(email); - handlePopUpClose('addMember'); - reset(); + handlePopUpClose('addMember'); + reset(); }; const onRemoveOrgMemberApproved = async () => { @@ -121,10 +124,14 @@ export const OrgMembersTable = ({ @@ -241,20 +254,23 @@ export const OrgMembersTable = ({ isOpen={popUp?.addMember?.isOpen} onOpenChange={(isOpen) => { handlePopUpToggle('addMember', isOpen); - reset(); + setCompleteInviteLink(undefined) }} > - An invite is specific to an email address and expires after 1 day. -
- For security reasons, you will need to separately add members to projects. - +
+ {!completeInviteLink &&
+ An invite is specific to an email address and expires after 1 day. +
+ For security reasons, you will need to separately add members to projects. +
} + {completeInviteLink && "This Infisical instance does not have a email provider setup. Please share this invite link with the invitee manually"} +
} > -
+ {!completeInviteLink && - + } + { + completeInviteLink && +
+

{completeInviteLink}

+ + + click to copy + +
+ }
Date: Fri, 17 Mar 2023 15:50:22 +0545 Subject: [PATCH 21/35] jest test basic setup --- backend/jest.config.ts | 2 +- backend/setupTests.ts | 11 ----------- backend/tests/{healthcheck.test.ts => setupTests.ts} | 0 3 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 backend/setupTests.ts rename backend/tests/{healthcheck.test.ts => setupTests.ts} (100%) diff --git a/backend/jest.config.ts b/backend/jest.config.ts index b7ca988db..8a72ad84c 100644 --- a/backend/jest.config.ts +++ b/backend/jest.config.ts @@ -5,5 +5,5 @@ export default { modulePaths: ['/src'], testMatch: ['/tests/**/*.test.ts'], setupFiles: ['/test-resources/env-vars.js'], - setupFilesAfterEnv: ['/setupTests.ts'] + setupFilesAfterEnv: ['/tests/setupTests.ts'] }; diff --git a/backend/setupTests.ts b/backend/setupTests.ts deleted file mode 100644 index c98a366f9..000000000 --- a/backend/setupTests.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { beforeAll, afterAll } from '@jest/globals'; -import { DatabaseService } from './src/services'; - -beforeAll(async () => { - const mongoURL = process.env.MONGO_URL!; - await DatabaseService.initDatabase(mongoURL); -}); - -afterAll(async () => { - await DatabaseService.closeDatabase(); -}); diff --git a/backend/tests/healthcheck.test.ts b/backend/tests/setupTests.ts similarity index 100% rename from backend/tests/healthcheck.test.ts rename to backend/tests/setupTests.ts From 9b2a31761a1b7e9dea9c767167c15c9cfd2b6b0e Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 17 Mar 2023 13:23:19 -0700 Subject: [PATCH 22/35] add default jwt life time --- backend/src/config/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 811327def..33066f613 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -5,12 +5,12 @@ export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!; export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10; export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d'; export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!; -export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')!; +export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')! || '5m'; export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m'; export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d'; export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!; export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!; -export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')!; +export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m'; export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!; export const getMongoURL = () => infisical.get('MONGO_URL')!; export const getNodeEnv = () => infisical.get('NODE_ENV')!; From 83e1900d89beba85cc5b52d9908a1abbc8631dfb Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 17 Mar 2023 15:29:49 -0700 Subject: [PATCH 23/35] get site url from request --- backend/src/controllers/v1/membershipOrgController.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index de85e2184..5a2b41b2b 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -102,6 +102,8 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { let invitee, inviteeMembershipOrg, completeInviteLink; try { const { organizationId, inviteeEmail } = req.body; + const host = req.headers.host; + const siteUrl = `${req.protocol}://${host}`; // validate membership const membershipOrg = await MembershipOrg.findOne({ @@ -183,7 +185,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { }); if (!getSmtpConfigured()) { - completeInviteLink = `${getSiteURL() + '/signupinvite'}?token=${token}&to=${inviteeEmail}` + completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}` } } From 809a55107314598e6afc57d31e2b4441d1fd1bcb Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Fri, 17 Mar 2023 15:42:22 -0700 Subject: [PATCH 24/35] Remove 1X1 call --- docs/getting-started/introduction.mdx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/docs/getting-started/introduction.mdx b/docs/getting-started/introduction.mdx index 38dcffe3a..cd5022083 100644 --- a/docs/getting-started/introduction.mdx +++ b/docs/getting-started/introduction.mdx @@ -30,6 +30,8 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical > Learn how to configure and deploy Infisical.
+ + Explore integrations for Docker, AWS, Heroku, etc. - - - - Our team is happy to help you get started with Infisical. If you have any - questions or want to learn how you can leverage Infisical within your - infrastructure, **[set up a 1-on-1 with an Infisical - engineer](https://cal.com/maidul/15min)**. - From 9e8e538647c7c498bbd65686d083c7a69d6a7867 Mon Sep 17 00:00:00 2001 From: Aashish-Upadhyay-101 Date: Sat, 18 Mar 2023 12:27:06 +0545 Subject: [PATCH 25/35] example test to resolve CI fail --- backend/tests/example.test.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 backend/tests/example.test.ts diff --git a/backend/tests/example.test.ts b/backend/tests/example.test.ts new file mode 100644 index 000000000..6d38025a5 --- /dev/null +++ b/backend/tests/example.test.ts @@ -0,0 +1,5 @@ +import { it, expect } from '@jest/globals'; + +it('should return true', () => { + expect(true).toBeTruthy(); +}); From 0b2e6a0d77ce931f0fca61dfb2093104281aa43a Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sat, 18 Mar 2023 20:56:40 -0700 Subject: [PATCH 26/35] Update self hosting docs --- .../infisical-ec2-deployment.template | 176 +++++++++++++++ docs/images/deploy-aws-button.png | Bin 0 -> 20885 bytes docs/mint.json | 26 +-- docs/self-hosting/configuration/email.mdx | 2 +- docs/self-hosting/configuration/envars.mdx | 186 ++++++++++++---- docs/self-hosting/overview.mdx | 206 ++++++++++++++++-- 6 files changed, 516 insertions(+), 80 deletions(-) create mode 100644 cloudformation/ec2-deployment/infisical-ec2-deployment.template create mode 100644 docs/images/deploy-aws-button.png diff --git a/cloudformation/ec2-deployment/infisical-ec2-deployment.template b/cloudformation/ec2-deployment/infisical-ec2-deployment.template new file mode 100644 index 000000000..36f0d9dce --- /dev/null +++ b/cloudformation/ec2-deployment/infisical-ec2-deployment.template @@ -0,0 +1,176 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + CloudFormation template to deploy Infisical on a EC2 instance with a + DocumentDB instance +Parameters: + KeyPairName: + Description: The name of the EC2 Key Pair to enable SSH access to the instance + Type: "AWS::EC2::KeyPair::KeyName" + VpcId: + Description: The ID of the VPC in which to launch the instance + Type: "AWS::EC2::VPC::Id" + DocumentDBUsername: + Description: The username for the DocumentDB instance + Type: String + MinLength: 5 + DocumentDBPassword: + Description: The password for the DocumentDB instance (minimum 8 characters) + Type: String + MinLength: 8 + NoEcho: true +Resources: + DocumentDBCluster: + Type: "AWS::DocDB::DBCluster" + Properties: + EngineVersion: 4.0.0 + StorageEncrypted: true + MasterUsername: !Ref DocumentDBUsername + MasterUserPassword: !Ref DocumentDBPassword + VpcSecurityGroupIds: + - !Ref DocumentDBClusterSecurityGroup + DBClusterParameterGroupName: !Ref DBClusterParameterGroup + Metadata: + "AWS::CloudFormation::Designer": + id: 73b974cf-eed3-4f7d-8657-6a6746bac169 + DependsOn: + - DBClusterParameterGroup + DBClusterParameterGroup: + Type: "AWS::DocDB::DBClusterParameterGroup" + Properties: + Description: "description" + Family: "docdb4.0" + Parameters: + tls: "disabled" + ttl_monitor: "disabled" + Tags: + - Key: "String" + Value: "String" + DocumentDBInstance: + Type: "AWS::DocDB::DBInstance" + Properties: + DBInstanceClass: db.t4g.medium + DBClusterIdentifier: !Ref DocumentDBCluster + Metadata: + "AWS::CloudFormation::Designer": + id: f04cee38-175e-4432-9ad7-62ca28bbf935 + DocumentDBClusterSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow inbound traffic for DocumentDB cluster + VpcId: !Ref VpcId + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 27017 + ToPort: 27017 + SourceSecurityGroupId: !Ref InstanceSecurityGroup + EC2Instance: + Type: "AWS::EC2::Instance" + Properties: + ImageId: ami-0557a15b87f6559cf + InstanceType: t2.medium + KeyName: !Ref KeyPairName + UserData: + Fn::Base64: !Sub | + #!/bin/bash + cd /home/ubuntu + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh + sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + git clone https://github.com/Infisical/infisical.git + cd infisical + + DOCUMENT_DB_CONNECTION_URL="mongodb://${DocumentDBUsername}:${DocumentDBPassword}@${DocumentDBCluster.Endpoint}:${DocumentDBCluster.Port}/infisical?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false" + + ENCRYPTION_KEY=$(openssl rand -hex 16) + JWT_SIGNUP_SECRET=$(openssl rand -hex 16) + JWT_REFRESH_SECRET=$(openssl rand -hex 16) + JWT_AUTH_SECRET=$(openssl rand -hex 16) + JWT_SERVICE_SECRET=$(openssl rand -hex 16) + + touch .env + + echo "ENCRYPTION_KEY=${!ENCRYPTION_KEY}" >> .env + echo "JWT_SIGNUP_SECRET=${!JWT_SIGNUP_SECRET}" >> .env + echo "JWT_REFRESH_SECRET=${!JWT_REFRESH_SECRET}" >> .env + echo "JWT_AUTH_SECRET=${!JWT_AUTH_SECRET}" >> .env + echo "JWT_SERVICE_SECRET=${!JWT_SERVICE_SECRET}" >> .env + echo "MONGO_URL=${!DOCUMENT_DB_CONNECTION_URL}" >> .env + + docker-compose up -d + + SecurityGroupIds: + - !Ref InstanceSecurityGroup + Tags: + - Key: Name + Value: infisical + Metadata: + "AWS::CloudFormation::Designer": + id: 2c0a771c-5002-4785-9848-0377e33cd0e9 + DependsOn: + - DocumentDBInstance + + InstanceSecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: Allow SSH and HTTP traffic + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + VpcId: !Ref VpcId + Metadata: + "AWS::CloudFormation::Designer": + id: 1fd6856a-11e5-4369-84fa-d18d4011b3de + +Outputs: + InstanceIP: + Value: !GetAtt EC2Instance.PublicIp +Metadata: + "AWS::CloudFormation::Designer": + 1fd6856a-11e5-4369-84fa-d18d4011b3de: + size: + width: 60 + height: 60 + position: + x: 60 + "y": 90 + z: 1 + embeds: [] + 2c0a771c-5002-4785-9848-0377e33cd0e9: + size: + width: 60 + height: 60 + position: + x: 180 + "y": 90 + z: 1 + embeds: [] + isassociatedwith: + - 1fd6856a-11e5-4369-84fa-d18d4011b3de + dependson: + - 2cabaada-fbdb-4945-bf95-a0406704dd5a + - f04cee38-175e-4432-9ad7-62ca28bbf935 + 73b974cf-eed3-4f7d-8657-6a6746bac169: + size: + width: 60 + height: 60 + position: + x: 390 + "y": 210 + z: 1 + embeds: [] + f04cee38-175e-4432-9ad7-62ca28bbf935: + size: + width: 60 + height: 60 + position: + x: 270 + "y": 90 + z: 1 + embeds: [] \ No newline at end of file diff --git a/docs/images/deploy-aws-button.png b/docs/images/deploy-aws-button.png new file mode 100644 index 0000000000000000000000000000000000000000..fd819989bca79eb6aa2f8eb9e98234c1f5b8300b GIT binary patch literal 20885 zcmeFXWkX!c5-5sG2u`rz?(R-Vbm-~YRz<*f}8~62iy-}U|@(+lA=mrU=X38_Bb32=(A(~dJ7EfL!E_) zh=P=e2$6!LovDSj2^g4USYk4)3i=_Y|9h$wO%{obL}TE^Y@$GH_e>IL~yOc-;5hAAhw= zWqr@!1yiW!B2lC${~(m4KokA(reKhgZ2IyQ44lRvECVd6H!(B?74;WbqSUL~qdS(6 z(NofxM!8?-yS`Y|V6r{9KZ%0eo>a3Y6`{yHY^5X}G+2q)=|Dt&F7uhFrbr}aXsvgo zGUY=rHf0QJFw+@2{?HdqEHH@FUeqE8p+=$88y#C3+z`C)wVAP$D7hISwd!hW9LT$B z3G@ntsKZ8=I75D6@lQ=DL*XfSa;1Tko;X;6*+L}4J3H^$G24F9YK7J;AvmqSEw*wb zRq|Yf?^$a_Fz95E5VDQI?NPRcQAy(v{5C7qqB?w zeS(%h`xoQFb*K$7oqlp}m9*DJV*lWeno#Rwl#GZ@-_a4aULccZ$^BC3t?{8v1GA(! zoAKjK=J4m71AghNWM63Oemx5<=h$4gag|qZVkeqHGCjr5G3Wb&yXbirSW-TpT0~P( zKlu5Y=JYTOMJb&m<}&WgtikoO%Irf)$gqS$C=o&O5rHlGBfTilgOkLcB^)>hx&H1BG-<2Zn3?OnyZ^xTTu$xb^UN~a1chV zeSrvuLA>+d8IkpGjGmwZv=Q)f_5b*|x8ps?$W<4KM;6u8mF1)!ujRzoXkhtUPfX{cq zZkRscc_RWKM>f?jP#{pqzXxNJaKJ$t5N}G7{0t0@E0Ba&ik=}tl>9s$u<~W+x77|S zOvo?81MJU{uoQ6UL7Rq-Ow!C&%!w&lGOT+Ldzgi=tf7z51>an1<}}>sK7Wpp?Puu0 zG#aV#s*zoYSm0d{IFZ0ZlITs{)^QMShSK4&Wwxcag{?$&!<>&g?nm6bbhl||UqdI1 z-rp#AFnr;70e``Hk@Y5gLl+MFCS*?pg@OkErRVDxZUezhwoQs(*-T$l$kWhP!!|_; zn6sO+nnM=E+vQG3qhn_z_sHax*r+q>1671AiFC>MDLT}ZlvtD;mDBRb6tpT;emP4X z)+)tJo7M6xhL4g=kdFL5{{8&hYzX=HIW@ZbVy8cN^j2k7^)rj~@&Vef?VgOEe&H%>sxY{GazVZ&X^@93GN|mHlbolT z>_a-!J)gX&nx1U=NfT`jvpDFIF6So#>4BvaMYev$-HXBS?~5 zkLR0B(;i?Q1O>MS3kS3G>IUlulZx+&GmFzk;YMX)$4BA}P!FU>El0u1ev~my$4Hw= z=VK#JW03*MG^M$wAf>0K_or5HSTTDURGN%)Ffvtf`dJItTr~c!DXwX@39B>KFx5D# zZa09|=GHFIRMpgN3a-#Cho0jtZ!`O5);xwXUYzPM5p3OCKh#jtbXZfc7`#+jZ&TyL z3N$v|8jWJ*HT7+BY8*wU)g4)K`{INN$nWDwktz&TVn<]qza1s`qMAOQ!Sl$=Mcz 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®V4|R0+8e4+kfWkW$rpzu9t~!_6 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 literal 0 HcmV?d00001 diff --git a/docs/mint.json b/docs/mint.json index e257ea0c5..48dc1805c 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -44,11 +44,6 @@ "icon": "shield-halved", "url": "security" }, - { - "name": "Self-hosting", - "icon": "server", - "url": "self-hosting" - }, { "name": "SDKs", "icon": "puzzle-piece", @@ -101,6 +96,14 @@ "getting-started/dashboard/token" ] }, + { + "group": "Self-hosting", + "pages": [ + "self-hosting/overview", + "self-hosting/configuration/envars", + "self-hosting/configuration/email" + ] + }, { "group": "Command line", "pages": [ @@ -171,12 +174,6 @@ "integrations/platforms/pm2" ] }, - { - "group": "Self-hosting", - "pages": [ - "self-hosting/overview" - ] - }, { "group": "Deployment options", "pages": [ @@ -184,13 +181,6 @@ "self-hosting/deployments/kubernetes" ] }, - { - "group": "Configuration", - "pages": [ - "self-hosting/configuration/envars", - "self-hosting/configuration/email" - ] - }, { "group": "Overview", "pages": [ diff --git a/docs/self-hosting/configuration/email.mdx b/docs/self-hosting/configuration/email.mdx index b35666776..a6b13fa0b 100644 --- a/docs/self-hosting/configuration/email.mdx +++ b/docs/self-hosting/configuration/email.mdx @@ -1,5 +1,5 @@ --- -title: "Email" +title: "Configure email service" description: "How to configure your email when self-hosting Infisical." --- diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index f674c1bcd..6786eef08 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -1,43 +1,151 @@ --- -title: "Environment Variables" -description: "How to configure your environment variables when self-hosting Infisical." +title: "All environment variables" +description: "Configure your environment variables when self-hosting Infisical." --- -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. +## Backend environment variables -| Variable | Description | Default Value | -| ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | -| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` | -| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` | -| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` | -| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` | -| `JWT_MFA_SECRET` | ❗️ JWT token secret | `None` | -| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` | -| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` | -| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` | -| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` | -| `JWT_MFA_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `5m` | -| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` | -| `MONGO_USERNAME` | MongoDB username if using container | `None` | -| `MONGO_PASSWORD` | MongoDB password if using container | `None` | -| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` | -| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` | -| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` | -| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` | -| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` | -| `SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` | -| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `team@infisical.com`) | `None` | -| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` | -| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` | -| `LICENSE_KEY` | License key if using Infisical Enterprise Edition | `true` | -| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` | -| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` | -| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` | -| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` | -| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` | -| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` | -| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` | -| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` | -| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` | -| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` | -| `INVITE_ONLY_SIGNUP` | If true, users can only sign up if they are invited | `false` | +Depending on your choosen self hosted deployment method, you may need to configured at least the required environment variable listed below. +Other environment variables are listed below to increase the functionality of your self hosted instance based on your use case. + + + + + Must be a random 32 character length hex string + + + + Must be a random 32 character length hex string + + + + Must be a random 32 character length hex string + + + + Must be a random 32 character length hex string + + + + Must be a random 32 character length hex string + + + + Must be a random 32 character length hex string + + + + *TLS based connection string is not yet supported + + + + When email service is not configured, Infisical will have limited functionality + + + Hostname to connect to for establishing SMTP connections + + + + Credential to connect to host (e.g. team@infisical.com) + + + + Credential to connect to host + + + + Port to connect to for establishing SMTP connections + + + + If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported + + + + Email address to be used for sending emails + + + + Name label to be used in From field (e.g. Team) + + + + To sync secret to third party services, provide value for the related services + + + OAuth2 client ID for Heroku integration + + + + OAuth2 client secret for Heroku integration + + + + OAuth2 client ID for Vercel integration + + + + OAuth2 client secret for Vercel integration + + + + OAuth2 client ID for Netlify integration + + + + OAuth2 client secret for Netlify integration + + + + OAuth2 client ID for GitHub integration + + + + OAuth2 client secret for GitHub integration + + + + OAuth2 slug for Netlify integration + + + + #### JWT + + JWT token lifetime expressed in seconds or a string describing a time span + + + + JWT token lifetime expressed in seconds or a string describing a time span + + + + JWT token lifetime expressed in seconds or a string describing a time span + + + + JWT token lifetime expressed in seconds or a string describing a time span + + + + + + + #### Error logging + Infisical uses Sentry to report error logs + + + #### Settings + + Only allow users who are invited to sign up + + + + Site URL - should be an absolute URL including the protocol (e.g. https://app.infisical.com) + + + + + + +## Frontend environment variables + diff --git a/docs/self-hosting/overview.mdx b/docs/self-hosting/overview.mdx index 7f3bc30bf..85d533b42 100644 --- a/docs/self-hosting/overview.mdx +++ b/docs/self-hosting/overview.mdx @@ -1,36 +1,198 @@ --- -title: "Overview" -description: "Infisical is an open-source end-to-end encrypted secrets manager that developers can set up within 15 minutes." +title: "Deployment options" +description: "Explore deployment options for self hosting Infisical" --- - -Self-host vs. Infisical Cloud +To meet various compliance requirements, may want to self-host Infisical instead of using [Infisical Cloud](https://app.infisical.com/). +Self-hosted Infisical allows you to maintain your sensitive information within your own infrastructure and network, ensuring complete control over your data. -Self-hosting Infisical means managing the service yourself, taking care of upgrades, scaling, security, etc. + + + + This deployment option will use AWS Cloudformation to auto deploy an instance of Infisical on a single EC2 via Docker Compose. -If you're less technical and looking for a hands-free experience with minimal overhead then we recommend Infisical Cloud. + **Resources that will be provisioned** + - 1 EC2 instance + - 1 DocumentDB cluster + - 1 DocumentDB instance + - Security groups -Infisical Cloud also comes with some extra features unavailable in the self-hosted edition. You can find more information about Infisical Cloud's offering on the pricing page. + + + - +
+ + This deployment option is highly available + Coming soon + + + This deployment option is highly available + + **Prerequisites** + - You have understanding of [Kubernetes](https://kubernetes.io/) + - You have understanding of [Helm package manager](https://helm.sh/) + - You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster -## Deployment options -Infisical can be deployed on a Linux VM with docker-compose and Kubernetes. We're rolling out more specific deployment options for DigitalOcean, AWS, GCP, and Azure soon. + #### 1. Fill our environment variables - - - Deploy to any Linux with Docker - - - Deploy to your Kubernetes cluster - - + Before you can deploy the Helm chart, you must fill out the required environment variables. To do so, please copy the below file to a `.yaml` file. + Refer to the available [environment variables](../../self-hosting/configuration/envars) to learn more -## Telemetry + + [View all available Helm chart values parameters](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical) + ```yaml + frontend: + enabled: true + name: frontend + podAnnotations: {} + deploymentAnnotations: {} + replicaCount: 2 + image: + repository: infisical/frontend + tag: "latest" + pullPolicy: IfNotPresent + kubeSecretRef: "" + service: + annotations: {} + type: ClusterIP + nodePort: "" -Infisical collects telemetry data about general usage. + frontendEnvironmentVariables: + SITE_URL: infisical.local -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. + backend: + enabled: true + name: backend + podAnnotations: {} + deploymentAnnotations: {} + replicaCount: 2 + image: + repository: infisical/backend + tag: "latest" + pullPolicy: IfNotPresent + kubeSecretRef: "" + service: + annotations: {} + type: ClusterIP + nodePort: "" -To opt out of telemetry, you can set `TELEMETRY_ENABLED=false` within the [environment variables](./configuration/envars). + backendEnvironmentVariables: + ENCRYPTION_KEY: MUST_REPLACE + JWT_SIGNUP_SECRET: MUST_REPLACE + JWT_REFRESH_SECRET: MUST_REPLACE + JWT_AUTH_SECRET: MUST_REPLACE + JWT_SERVICE_SECRET: MUST_REPLACE + SMTP_HOST: MUST_REPLACE + SMTP_PORT: 587 + SMTP_SECURE: false + SMTP_FROM_NAME: Infisical + SMTP_FROM_ADDRESS: MUST_REPLACE + SMTP_USERNAME: MUST_REPLACE + SMTP_PASSWORD: MUST_REPLACE + SITE_URL: infisical.local + + ## Mongo DB persistence + mongodb: + enabled: true + + ## By default the backend will be connected to a Mongo instance within the cluster + ## However, it is recommended to add a managed document DB connection string for production-use (DBaaS) + ## Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/ + ## e.g. "mongodb://:@:/" + mongodbConnection: + externalMongoDBConnectionString: "" + + ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: "nginx" + # cert-manager.io/issuer: letsencrypt-nginx + hostName: infisical.local ## <- Replace with your own domain + frontend: + path: / + pathType: Prefix + backend: + path: /api + pathType: Prefix + tls: [] + # - secretName: letsencrypt-nginx + # hosts: + # - infisical.local + + mailhog: + enabled: false + ``` + + + Once you have a local copy of the values file, fill our the required environment variables and save the file. + + + #### 2. Install Infisical Helm repository + + ```bash + helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' + + helm repo update + ``` + + #### 3. Install the Helm chart + + By default, the helm chart will be installed on your default namespace. If you wish to install the Chart on a different namespace, you may specify + that by adding the `--namespace ` to your `helm install` command. + + ```bash + ## Installs to default namespace + helm install infisical-helm-charts/infisical --generate-name --values + ``` + + + If you have not filled out all of the required environment variables, you will see an error message prompting you to + do so. + + + #### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`. + + + + 1. Install Docker on your VM + + ```bash + # Example in ubuntu + apt-get update + apt-get upgrade + apt install docker-compose + ``` + + 2. Download the required files + + ```bash + # Download env file template + wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example + + # Download docker compose template + wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml + + # Download nginx config + mkdir nginx && wget -O ./nginx/default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf + ``` + + 3. Tweak the `.env` according to your preferences. Refer to the available [environment variables](../../self-hosting/configuration/envars) + + ```bash + # update environment variables like mongo login + nano .env + ``` + + 4. Get the service up and running. + + ```bash + # Start up services in detached mode + docker-compose -f docker-compose.yml up -d + ``` + + 5. Your Infisical installation is complete and should be running on [http://localhost:80](http://localhost:80). Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures. + + + \ No newline at end of file From b944e8bb84dc111324c16255d4b5437cf882dcc5 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sat, 18 Mar 2023 21:30:05 -0700 Subject: [PATCH 27/35] fix typo in self host docs --- docs/self-hosting/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/self-hosting/overview.mdx b/docs/self-hosting/overview.mdx index 85d533b42..216b02d0e 100644 --- a/docs/self-hosting/overview.mdx +++ b/docs/self-hosting/overview.mdx @@ -3,7 +3,7 @@ title: "Deployment options" description: "Explore deployment options for self hosting Infisical" --- -To meet various compliance requirements, may want to self-host Infisical instead of using [Infisical Cloud](https://app.infisical.com/). +To meet various compliance requirements, you may want to self-host Infisical instead of using [Infisical Cloud](https://app.infisical.com/). Self-hosted Infisical allows you to maintain your sensitive information within your own infrastructure and network, ensuring complete control over your data. From 18bbe09af4b397230685469223297724b2995587 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 19 Mar 2023 10:46:28 -0700 Subject: [PATCH 28/35] remove background from aws deploy btn --- docs/images/deploy-aws-button.png | Bin 20885 -> 18295 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/deploy-aws-button.png b/docs/images/deploy-aws-button.png index fd819989bca79eb6aa2f8eb9e98234c1f5b8300b..71d484c6a7c3e99100874660cebfba3bdd4b460c 100644 GIT binary patch literal 18295 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 gkRvzopr0Dv&4dH?_b literal 20885 zcmeFXWkX!c5-5sG2u`rz?(R-Vbm-~YRz<*f}8~62iy-}U|@(+lA=mrU=X38_Bb32=(A(~dJ7EfL!E_) zh=P=e2$6!LovDSj2^g4USYk4)3i=_Y|9h$wO%{obL}TE^Y@$GH_e>IL~yOc-;5hAAhw= zWqr@!1yiW!B2lC${~(m4KokA(reKhgZ2IyQ44lRvECVd6H!(B?74;WbqSUL~qdS(6 z(NofxM!8?-yS`Y|V6r{9KZ%0eo>a3Y6`{yHY^5X}G+2q)=|Dt&F7uhFrbr}aXsvgo zGUY=rHf0QJFw+@2{?HdqEHH@FUeqE8p+=$88y#C3+z`C)wVAP$D7hISwd!hW9LT$B z3G@ntsKZ8=I75D6@lQ=DL*XfSa;1Tko;X;6*+L}4J3H^$G24F9YK7J;AvmqSEw*wb zRq|Yf?^$a_Fz95E5VDQI?NPRcQAy(v{5C7qqB?w zeS(%h`xoQFb*K$7oqlp}m9*DJV*lWeno#Rwl#GZ@-_a4aULccZ$^BC3t?{8v1GA(! zoAKjK=J4m71AghNWM63Oemx5<=h$4gag|qZVkeqHGCjr5G3Wb&yXbirSW-TpT0~P( zKlu5Y=JYTOMJb&m<}&WgtikoO%Irf)$gqS$C=o&O5rHlGBfTilgOkLcB^)>hx&H1BG-<2Zn3?OnyZ^xTTu$xb^UN~a1chV zeSrvuLA>+d8IkpGjGmwZv=Q)f_5b*|x8ps?$W<4KM;6u8mF1)!ujRzoXkhtUPfX{cq zZkRscc_RWKM>f?jP#{pqzXxNJaKJ$t5N}G7{0t0@E0Ba&ik=}tl>9s$u<~W+x77|S zOvo?81MJU{uoQ6UL7Rq-Ow!C&%!w&lGOT+Ldzgi=tf7z51>an1<}}>sK7Wpp?Puu0 zG#aV#s*zoYSm0d{IFZ0ZlITs{)^QMShSK4&Wwxcag{?$&!<>&g?nm6bbhl||UqdI1 z-rp#AFnr;70e``Hk@Y5gLl+MFCS*?pg@OkErRVDxZUezhwoQs(*-T$l$kWhP!!|_; zn6sO+nnM=E+vQG3qhn_z_sHax*r+q>1671AiFC>MDLT}ZlvtD;mDBRb6tpT;emP4X z)+)tJo7M6xhL4g=kdFL5{{8&hYzX=HIW@ZbVy8cN^j2k7^)rj~@&Vef?VgOEe&H%>sxY{GazVZ&X^@93GN|mHlbolT z>_a-!J)gX&nx1U=NfT`jvpDFIF6So#>4BvaMYev$-HXBS?~5 zkLR0B(;i?Q1O>MS3kS3G>IUlulZx+&GmFzk;YMX)$4BA}P!FU>El0u1ev~my$4Hw= z=VK#JW03*MG^M$wAf>0K_or5HSTTDURGN%)Ffvtf`dJItTr~c!DXwX@39B>KFx5D# zZa09|=GHFIRMpgN3a-#Cho0jtZ!`O5);xwXUYzPM5p3OCKh#jtbXZfc7`#+jZ&TyL z3N$v|8jWJ*HT7+BY8*wU)g4)K`{INN$nWDwktz&TVn<]qza1s`qMAOQ!Sl$=Mcz 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®V4|R0+8e4+kfWkW$rpzu9t~!_6 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 From 119600b64f48d8cc6bf39bd28385a05e6e92e651 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 20 Mar 2023 10:19:09 -0700 Subject: [PATCH 29/35] remove site url env var from frontend --- frontend/src/components/utilities/config/index.ts | 2 -- frontend/src/components/utilities/generateBackupPDF.ts | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/utilities/config/index.ts b/frontend/src/components/utilities/config/index.ts index 5a55658e4..7570785ba 100644 --- a/frontend/src/components/utilities/config/index.ts +++ b/frontend/src/components/utilities/config/index.ts @@ -4,13 +4,11 @@ const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com"; const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!; const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!; -const SITE_URL = "https://app.infisical.com"; export { ENV, POSTHOG_API_KEY, POSTHOG_HOST, - SITE_URL, STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER }; \ No newline at end of file diff --git a/frontend/src/components/utilities/generateBackupPDF.ts b/frontend/src/components/utilities/generateBackupPDF.ts index 3e2246db6..41fb49f13 100644 --- a/frontend/src/components/utilities/generateBackupPDF.ts +++ b/frontend/src/components/utilities/generateBackupPDF.ts @@ -1,7 +1,5 @@ import { jsPDF } from 'jspdf'; -import { SITE_URL } from './config'; - interface PDFProps { personalName: string; personalEmail: string; @@ -19,7 +17,7 @@ const yyyy = today.getFullYear(); const todayFormatted = `${mm}/${dd}/${yyyy}`; -function createPdfHeader(doc: jsPDF, personalName : string) { +function createPdfHeader(doc: jsPDF, personalName: string) { doc.setFillColor(255, 255, 255); doc.rect(0, 0, 600, 900, 'F'); doc.setTextColor(23, 23, 23); @@ -30,6 +28,10 @@ function createPdfHeader(doc: jsPDF, personalName : string) { } function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: string) { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== '80' ? `:${port}` : ''; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + doc.setFontSize(14); doc.text( 'In case you get locked out of you Infisical account, you`ll need these account details', @@ -73,7 +75,7 @@ function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: strin doc.roundedRect(170, 488, 375, 35, 5, 5, 'F'); doc.setTextColor(23, 23, 23); doc.setFontSize(14); - doc.text(`${SITE_URL}/login`, 180, 420); + doc.text(`${siteURL}/login`, 180, 420); doc.text(personalEmail, 180, 465); doc.text(generatedKey, 180, 510); doc.text('Need help? Contact us at support@infisical.com', 32, 575); From 1cc882462ee31589581d45ec0d4d5f5b11ad7fa0 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Mon, 20 Mar 2023 19:12:46 +0000 Subject: [PATCH 30/35] fix: upgrade @sentry/node from 7.38.0 to 7.39.0 Snyk has created this PR to upgrade @sentry/node from 7.38.0 to 7.39.0. See this package in npm: https://www.npmjs.com/package/@sentry/node See this project in Snyk: https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr --- backend/package-lock.json | 83 +++++++++++++++++++++++++++++++++------ backend/package.json | 2 +- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 1eb1dbc66..e6b8daaf0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -12,7 +12,7 @@ "@aws-sdk/client-secrets-manager": "^3.267.0", "@godaddy/terminus": "^4.11.2", "@octokit/rest": "^19.0.5", - "@sentry/node": "^7.14.0", + "@sentry/node": "^7.39.0", "@sentry/tracing": "^7.19.0", "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", @@ -2800,13 +2800,13 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/node": { - "version": "7.38.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.38.0.tgz", - "integrity": "sha512-jNIN6NZvgzn/oms8RQzffjX8Z0LQDTN6N28nnhzqGCvnfmS1QtTt0FlU+pTuFXZNNSjfGy4XMXMYvLlbvhm2bg==", + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.39.0.tgz", + "integrity": "sha512-oe1OBxgs6t/FizjxkSPtuvJv5wJMO+mLENZkiE0PpBD56JyZrWK48kYIt2ccWAfk6Vh235/oIpmqET150xB4lQ==", "dependencies": { - "@sentry/core": "7.38.0", - "@sentry/types": "7.38.0", - "@sentry/utils": "7.38.0", + "@sentry/core": "7.39.0", + "@sentry/types": "7.39.0", + "@sentry/utils": "7.39.0", "cookie": "^0.4.1", "https-proxy-agent": "^5.0.0", "lru_map": "^0.3.3", @@ -2816,6 +2816,39 @@ "node": ">=8" } }, + "node_modules/@sentry/node/node_modules/@sentry/core": { + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.39.0.tgz", + "integrity": "sha512-45WJIcWWCQnZ8zhHtcrkJjQ4YydmzMWY4pmRuBG7Qp+zrCT6ISoyODcjY+SCHFdvXkiYFi8+bFZa1qG3YQnnYw==", + "dependencies": { + "@sentry/types": "7.39.0", + "@sentry/utils": "7.39.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/types": { + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.39.0.tgz", + "integrity": "sha512-5Y83Y8O3dT5zT2jTKEIPMcpn5lUm05KRMaCXuw0sRsv4r9TbBUKeqiSU1LjowT8rB/XNy8m7DHav8+NmogPaJw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/utils": { + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.39.0.tgz", + "integrity": "sha512-/ZxlPgm1mGgmuMckCTc9iyqDuFTEYNEoMB53IjVFz8ann+37OiWB7Py/QV1rEEsv3xKrGbA8thhRhV9E1sjTlQ==", + "dependencies": { + "@sentry/types": "7.39.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@sentry/node/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -14361,19 +14394,43 @@ } }, "@sentry/node": { - "version": "7.38.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.38.0.tgz", - "integrity": "sha512-jNIN6NZvgzn/oms8RQzffjX8Z0LQDTN6N28nnhzqGCvnfmS1QtTt0FlU+pTuFXZNNSjfGy4XMXMYvLlbvhm2bg==", + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.39.0.tgz", + "integrity": "sha512-oe1OBxgs6t/FizjxkSPtuvJv5wJMO+mLENZkiE0PpBD56JyZrWK48kYIt2ccWAfk6Vh235/oIpmqET150xB4lQ==", "requires": { - "@sentry/core": "7.38.0", - "@sentry/types": "7.38.0", - "@sentry/utils": "7.38.0", + "@sentry/core": "7.39.0", + "@sentry/types": "7.39.0", + "@sentry/utils": "7.39.0", "cookie": "^0.4.1", "https-proxy-agent": "^5.0.0", "lru_map": "^0.3.3", "tslib": "^1.9.3" }, "dependencies": { + "@sentry/core": { + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.39.0.tgz", + "integrity": "sha512-45WJIcWWCQnZ8zhHtcrkJjQ4YydmzMWY4pmRuBG7Qp+zrCT6ISoyODcjY+SCHFdvXkiYFi8+bFZa1qG3YQnnYw==", + "requires": { + "@sentry/types": "7.39.0", + "@sentry/utils": "7.39.0", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.39.0.tgz", + "integrity": "sha512-5Y83Y8O3dT5zT2jTKEIPMcpn5lUm05KRMaCXuw0sRsv4r9TbBUKeqiSU1LjowT8rB/XNy8m7DHav8+NmogPaJw==" + }, + "@sentry/utils": { + "version": "7.39.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.39.0.tgz", + "integrity": "sha512-/ZxlPgm1mGgmuMckCTc9iyqDuFTEYNEoMB53IjVFz8ann+37OiWB7Py/QV1rEEsv3xKrGbA8thhRhV9E1sjTlQ==", + "requires": { + "@sentry/types": "7.39.0", + "tslib": "^1.9.3" + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", diff --git a/backend/package.json b/backend/package.json index f24ef80fa..17111f8c6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -3,7 +3,7 @@ "@aws-sdk/client-secrets-manager": "^3.267.0", "@godaddy/terminus": "^4.11.2", "@octokit/rest": "^19.0.5", - "@sentry/node": "^7.14.0", + "@sentry/node": "^7.39.0", "@sentry/tracing": "^7.19.0", "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", From 2916c05101670fb6f2fde85fbafda2fc551aa7f0 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Mon, 20 Mar 2023 19:12:49 +0000 Subject: [PATCH 31/35] fix: upgrade posthog-node from 2.5.3 to 2.5.4 Snyk has created this PR to upgrade posthog-node from 2.5.3 to 2.5.4. See this package in npm: https://www.npmjs.com/package/posthog-node See this project in Snyk: https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 1eb1dbc66..4568254b4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -40,7 +40,7 @@ "lodash": "^4.17.21", "mongoose": "^6.7.2", "nodemailer": "^6.8.0", - "posthog-node": "^2.2.2", + "posthog-node": "^2.5.4", "query-string": "^7.1.3", "request-ip": "^3.3.0", "rimraf": "^3.0.2", @@ -10500,9 +10500,9 @@ } }, "node_modules/posthog-node": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.3.tgz", - "integrity": "sha512-kDmBjQHguPrh/rUTKmB0+Hj7C3fq2t+/fcfQkDBGz0f0fEF2WxV5yyxRmd2IF/hFmHxMrGLDkEVjKr78B+judg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.4.tgz", + "integrity": "sha512-CdywlVh0CZU05/3MrBc0qY/zsLdU2X9XSz/yL1qMRhbyZhD8lrnuGlI69G2cpzZtli6S/nu64wcmULz/mFFA5w==", "dependencies": { "axios": "^0.27.0" }, @@ -20049,9 +20049,9 @@ } }, "posthog-node": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.3.tgz", - "integrity": "sha512-kDmBjQHguPrh/rUTKmB0+Hj7C3fq2t+/fcfQkDBGz0f0fEF2WxV5yyxRmd2IF/hFmHxMrGLDkEVjKr78B+judg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.4.tgz", + "integrity": "sha512-CdywlVh0CZU05/3MrBc0qY/zsLdU2X9XSz/yL1qMRhbyZhD8lrnuGlI69G2cpzZtli6S/nu64wcmULz/mFFA5w==", "requires": { "axios": "^0.27.0" }, diff --git a/backend/package.json b/backend/package.json index f24ef80fa..cc23cd087 100644 --- a/backend/package.json +++ b/backend/package.json @@ -31,7 +31,7 @@ "lodash": "^4.17.21", "mongoose": "^6.7.2", "nodemailer": "^6.8.0", - "posthog-node": "^2.2.2", + "posthog-node": "^2.5.4", "query-string": "^7.1.3", "request-ip": "^3.3.0", "rimraf": "^3.0.2", From 6e3ee3f4a657b28b1eebf593f521cc9ed3aa1bb8 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Mon, 20 Mar 2023 19:12:53 +0000 Subject: [PATCH 32/35] fix: upgrade swagger-ui-express from 4.6.1 to 4.6.2 Snyk has created this PR to upgrade swagger-ui-express from 4.6.1 to 4.6.2. See this package in npm: https://www.npmjs.com/package/swagger-ui-express See this project in Snyk: https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 1eb1dbc66..f32c997eb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -46,7 +46,7 @@ "rimraf": "^3.0.2", "stripe": "^10.7.0", "swagger-autogen": "^2.22.0", - "swagger-ui-express": "^4.6.0", + "swagger-ui-express": "^4.6.2", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", "typescript": "^4.9.3", @@ -11483,9 +11483,9 @@ "integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w==" }, "node_modules/swagger-ui-express": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.1.tgz", - "integrity": "sha512-Pss7YNFKNdq66XKNjRe4IRXKKYNx/LvOSml9TdrZ8/78UpxUHIp9JoXpXWA5Z4L+SCmX63DZ9IPlQ8nnRuncvA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz", + "integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==", "dependencies": { "swagger-ui-dist": ">=4.11.0" }, @@ -20779,9 +20779,9 @@ "integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w==" }, "swagger-ui-express": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.1.tgz", - "integrity": "sha512-Pss7YNFKNdq66XKNjRe4IRXKKYNx/LvOSml9TdrZ8/78UpxUHIp9JoXpXWA5Z4L+SCmX63DZ9IPlQ8nnRuncvA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz", + "integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==", "requires": { "swagger-ui-dist": ">=4.11.0" } diff --git a/backend/package.json b/backend/package.json index f24ef80fa..101ed2842 100644 --- a/backend/package.json +++ b/backend/package.json @@ -37,7 +37,7 @@ "rimraf": "^3.0.2", "stripe": "^10.7.0", "swagger-autogen": "^2.22.0", - "swagger-ui-express": "^4.6.0", + "swagger-ui-express": "^4.6.2", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1", "typescript": "^4.9.3", From 66fae1fa0a68ee373d3da9d64d1d6f65f88213dc Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 20 Mar 2023 12:51:03 -0700 Subject: [PATCH 33/35] Update docker.mdx --- docs/integrations/platforms/docker.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/platforms/docker.mdx b/docs/integrations/platforms/docker.mdx index 5220833d8..55422d8ba 100644 --- a/docs/integrations/platforms/docker.mdx +++ b/docs/integrations/platforms/docker.mdx @@ -55,7 +55,7 @@ Head to your project settings in Infisical Cloud to generate an [Infisical Token ## Feed Docker your Infisical Token ```bash - docker run --env INFISICAL_TOKEN=[token]... + docker run --env INFISICAL_TOKEN=[token] [DOCKER-IMAGE]... ``` From ed73ded05fcfce0baf2a523d35947e01ed7724cb Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 20 Mar 2023 12:52:28 -0700 Subject: [PATCH 34/35] Update docker.mdx --- docs/integrations/platforms/docker.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/platforms/docker.mdx b/docs/integrations/platforms/docker.mdx index 55422d8ba..a19562a99 100644 --- a/docs/integrations/platforms/docker.mdx +++ b/docs/integrations/platforms/docker.mdx @@ -39,10 +39,10 @@ Prerequisites: Starting your service with the Infisical CLI pulls your secrets from Infisical and injects them into your service. ```dockerfile -CMD ["infisical", "run", "---", "[your service start command]"] +CMD ["infisical", "run", "--", "[your service start command]"] # example with single single command -CMD ["infisical", "run", "---", "npm", "run", "start"] +CMD ["infisical", "run", "--", "npm", "run", "start"] # example with multiple commands CMD ["infisical", "run", "--command", "npm run start && ..."] From 88a208f47e8bae28872a7de6a6a52328a85f296a Mon Sep 17 00:00:00 2001 From: mv-turtle <78047717+mv-turtle@users.noreply.github.com> Date: Mon, 20 Mar 2023 22:44:57 -0700 Subject: [PATCH 35/35] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5014906c1..449b8cce6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ git commit activity - Cloudsmith downloads + Cloudsmith downloads Slack community channel