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/__tests__/healthcheck.test.ts b/backend/__tests__/healthcheck.test.ts index 234d2d8eb..e054bd180 100644 --- a/backend/__tests__/healthcheck.test.ts +++ b/backend/__tests__/healthcheck.test.ts @@ -1,19 +1,21 @@ -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'; +import request from 'supertest'; + +let server: Server; + +beforeAll(async () => { + server = await main; +}); + +afterAll(async () => { + server.close(); +}); -const requestWithSupertest = supertest(server); describe('Healthcheck endpoint', () => { - beforeAll(async () => { - setUpHealthEndpoint(server); - }); - afterAll(async () => { - server.close(); - }); - 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/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..1eb1dbc66 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.37", "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.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/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.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" + } + }, "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..cd6cd214a 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.37", "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..3fe935097 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,105 +1,50 @@ -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!; - -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 -}; +import infisical from 'infisical-node'; +export const getPort = () => infisical.get('PORT')! || 4000; +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'; +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; +export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; \ No newline at end of file diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index e329b15c7..3c6ec523a 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -1,8 +1,8 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +import * as Sentry from '@sentry/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,15 +11,15 @@ 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 +import { + getNodeEnv, + getJwtRefreshSecret, + getJwtAuthLifetime, + getJwtAuthSecret +} from '../../config'; declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -126,7 +126,7 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); const loginAction = await EELogService.createAction({ @@ -182,7 +182,7 @@ export const logout = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); const logoutAction = await EELogService.createAction({ @@ -237,7 +237,7 @@ export const getNewToken = async (req: Request, res: Response) => { } const decodedToken = ( - jwt.verify(refreshToken, JWT_REFRESH_SECRET) + jwt.verify(refreshToken, getJwtRefreshSecret()) ); const user = await User.findOne({ @@ -252,8 +252,8 @@ export const getNewToken = async (req: Request, res: Response) => { payload: { userId: decodedToken.userId }, - expiresIn: JWT_AUTH_LIFETIME, - secret: JWT_AUTH_SECRET + expiresIn: getJwtAuthLifetime(), + secret: getJwtAuthSecret() }); 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..436be9dc4 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -1,13 +1,13 @@ -import { Request, Response } from 'express'; 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'; +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: 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 aa3022eac..105be0503 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -1,6 +1,5 @@ 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'; @@ -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: 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: JWT_SIGNUP_LIFETIME, - secret: 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 66326e560..00ad87b82 100644 --- a/backend/src/controllers/v1/organizationController.ts +++ b/backend/src/controllers/v1/organizationController.ts @@ -1,26 +1,18 @@ -import { Request, Response } from 'express'; 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'; import { OWNER, ACCEPTED } from '../../variables'; import _ from 'lodash'; +import { getStripeSecretKey, getSiteURL } from '../../config'; export const getOrganizations = async (req: Request, res: Response) => { let organizations; @@ -325,6 +317,10 @@ export const createOrganizationPortalSession = async ( ) => { let session; try { + const stripe = new Stripe(getStripeSecretKey(), { + 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: getSiteURL() + '/dashboard', + cancel_url: getSiteURL() + '/dashboard' }); } else { session = await stripe.billingPortal.sessions.create({ customer: req.membershipOrg.organization.customerId, - return_url: SITE_URL + '/dashboard' + return_url: getSiteURL() + '/dashboard' }); } @@ -369,6 +365,10 @@ export const getOrganizationSubscriptions = async ( ) => { let subscriptions; try { + const stripe = new Stripe(getStripeSecretKey(), { + 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..fed24419c 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -7,9 +7,9 @@ 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'; +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: 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: JWT_SIGNUP_LIFETIME, - secret: JWT_SIGNUP_SECRET + expiresIn: getJwtSignupLifetime(), + secret: getJwtSignupSecret() }); } 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..9f241349a 100644 --- a/backend/src/controllers/v1/serviceTokenController.ts +++ b/backend/src/controllers/v1/serviceTokenController.ts @@ -1,7 +1,7 @@ import { Request, Response } from 'express'; import { ServiceToken } from '../../models'; import { createToken } from '../../helpers/auth'; -import { JWT_SERVICE_SECRET } from '../../config'; +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: 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 1c5c9e298..cb411a29e 100644 --- a/backend/src/controllers/v1/signupController.ts +++ b/backend/src/controllers/v1/signupController.ts @@ -1,13 +1,13 @@ 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, } 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 (INVITE_ONLY_SIGNUP) { + 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: JWT_SIGNUP_LIFETIME, - secret: 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 15f62a056..1a981c088 100644 --- a/backend/src/controllers/v1/stripeController.ts +++ b/backend/src/controllers/v1/stripeController.ts @@ -1,10 +1,7 @@ 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' -}); +import { getStripeSecretKey, getStripeWebhookSecret } from '../../config'; /** * 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(getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); + const sig = req.headers['stripe-signature'] as string; event = stripe.webhooks.constructEvent( req.body, sig, - 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 cafbacb5b..fd87f7306 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 { Request, Response } from 'express'; import crypto from 'crypto'; import bcrypt from 'bcrypt'; import { APIKeyData } from '../../models'; -import { - SALT_ROUNDS -} from '../../config'; +import { getSaltRounds } 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, 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 95a1613d2..b0204e1b9 100644 --- a/backend/src/controllers/v2/authController.ts +++ b/backend/src/controllers/v2/authController.ts @@ -10,17 +10,17 @@ 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, 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 { @@ -28,8 +28,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 +124,8 @@ export const login2 = async (req: Request, res: Response) => { payload: { userId: user._id.toString() }, - expiresIn: JWT_MFA_LIFETIME, - secret: JWT_MFA_SECRET + expiresIn: getJwtMfaLifetime(), + secret: getJwtMfaSecret() }); const code = await TokenService.createToken({ @@ -165,7 +163,7 @@ export const login2 = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); // case: user does not have MFA enablgged @@ -304,7 +302,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: NODE_ENV === 'production' ? true : false + secure: getNodeEnv() === 'production' ? true : false }); interface VerifyMfaTokenRes { @@ -347,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/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..a4e06f8e4 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -1,15 +1,13 @@ -import { Request, Response } from 'express'; import * as Sentry from '@sentry/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'; +import { getSaltRounds } from '../../config'; /** * Return service token data associated with service token on request @@ -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, getSaltRounds()); const expiresAt = new Date(); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); @@ -142,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 79cb6730d..aaf055b78 100644 --- a/backend/src/controllers/v2/signupController.ts +++ b/backend/src/controllers/v2/signupController.ts @@ -7,8 +7,8 @@ import { } from '../../helpers/signup'; import { issueAuthTokens } from '../../helpers/auth'; import { INVITED, ACCEPTED } from '../../variables'; -import { NODE_ENV } from '../../config'; import request from '../../config/request'; +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() }, }); } @@ -127,7 +127,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => { httpOnly: true, path: '/', sameSite: 'strict', - secure: 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: NODE_ENV === 'production' ? true : false + secure: getNodeEnv() === '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..3caa0f395 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 { 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' -}); +import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config'; /** * 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(getStripeSecretKey(), { + 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 // ? + getStripeWebhookSecret() ); } 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..a08dcf2cc 100644 --- a/backend/src/helpers/auth.ts +++ b/backend/src/helpers/auth.ts @@ -1,5 +1,5 @@ -import jwt from 'jsonwebtoken'; import * as Sentry from '@sentry/node'; +import jwt from 'jsonwebtoken'; import bcrypt from 'bcrypt'; import { IUser, @@ -7,12 +7,6 @@ import { ServiceTokenData, APIKeyData } from '../models'; -import { - JWT_AUTH_LIFETIME, - JWT_AUTH_SECRET, - JWT_REFRESH_LIFETIME, - JWT_REFRESH_SECRET -} from '../config'; import { AccountNotFoundError, ServiceTokenDataNotFoundError, @@ -20,6 +14,12 @@ import { UnauthorizedRequestError, BadRequestError } from '../utils/errors'; +import { + getJwtAuthLifetime, + getJwtAuthSecret, + getJwtRefreshLifetime, + getJwtRefreshSecret +} from '../config'; /** * @@ -93,7 +93,7 @@ const getAuthUserPayload = async ({ let user; try { const decodedToken = ( - jwt.verify(authTokenValue, JWT_AUTH_SECRET) + jwt.verify(authTokenValue, getJwtAuthSecret()) ); user = await User.findOne({ @@ -224,16 +224,16 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => { payload: { userId }, - expiresIn: JWT_AUTH_LIFETIME, - secret: JWT_AUTH_SECRET + expiresIn: getJwtAuthLifetime(), + secret: getJwtAuthSecret() }); refreshToken = createToken({ payload: { userId }, - expiresIn: JWT_REFRESH_LIFETIME, - secret: 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 7519ef18b..5cfbeebf5 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -12,8 +12,8 @@ import { decryptSymmetric, decryptAsymmetric } from '../utils/crypto'; -import { ENCRYPTION_KEY } from '../config'; 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: 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: ENCRYPTION_KEY + key: getEncryptionKey() }); key = decryptAsymmetric({ 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/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 958342aae..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 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'; +import { getSmtpFromName, getSmtpFromAddress } from '../config'; 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: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, to: recipients.join(', '), subject: subjectLine, html: htmlToSend diff --git a/backend/src/helpers/organization.ts b/backend/src/helpers/organization.ts index 4ba3592de..fb559df1b 100644 --- a/backend/src/helpers/organization.ts +++ b/backend/src/helpers/organization.ts @@ -1,23 +1,14 @@ 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 -}; +import { + getStripeSecretKey, + getStripeProductPro, + getStripeProductTeam, + getStripeProductStarter +} from '../config'; /** * Create an organization with name [name] @@ -36,8 +27,11 @@ const createOrganization = async ({ let organization; try { // register stripe account + const stripe = new Stripe(getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); - if (STRIPE_SECRET_KEY) { + if (getStripeSecretKey()) { const customer = await stripe.customers.create({ email, description: name @@ -87,6 +81,16 @@ const initSubscriptionOrg = async ({ if (organization) { if (organization.customerId) { // initialize starter subscription with quantity of 0 + const stripe = new Stripe(getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); + + const productToPriceMap = { + starter: getStripeProductStarter(), + team: getStripeProductTeam(), + pro: getStripeProductPro() + }; + stripeSubscription = await stripe.subscriptions.create({ customer: organization.customerId, items: [ @@ -139,6 +143,10 @@ const updateSubscriptionOrgQuantity = async ({ status: ACCEPTED }); + const stripe = new Stripe(getStripeSecretKey(), { + apiVersion: '2022-08-01' + }); + const subscription = ( await stripe.subscriptions.list({ customer: organization.customerId @@ -167,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 ca8838151..0f6a88cc9 100644 --- a/backend/src/helpers/token.ts +++ b/backend/src/helpers/token.ts @@ -9,10 +9,8 @@ import { TOKEN_EMAIL_ORG_INVITATION, TOKEN_EMAIL_PASSWORD_RESET } from '../variables'; -import { - SALT_ROUNDS -} from '../config'; import { UnauthorizedRequestError } from '../utils/errors'; +import { getSaltRounds } from '../config'; /** * Create and store a token in the database for purpose [type] @@ -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, getSaltRounds()), expiresAt } diff --git a/backend/src/index.ts b/backend/src/index.ts index a97b5b33b..64d58cfc5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,28 +1,181 @@ +import mongoose from 'mongoose'; 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'; +import { + getMongoURL, + getNodeEnv, + getPort, + getSentryDSN, + getSiteURL +} from './config'; -setUpHealthEndpoint(server); +const main = async () => { + await infisical.connect({ + token: process.env.INFISICAL_TOKEN! + }); + + logTelemetryMessage(); + setTransporter(initSmtp()); -setTransporter(initSmtp()); + await DatabaseService.initDatabase(getMongoURL()); + if (getNodeEnv() !== 'test') { + Sentry.init({ + dsn: getSentryDSN(), + tracesSampleRate: 1.0, + debug: getNodeEnv() === 'production' ? false : true, + environment: getNodeEnv() + }); + } -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: getSiteURL() + }) + ); + + app.use(requestIp.mw()); + + if (getNodeEnv() === '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(getPort(), () => { + getLogger("backend-main").info(`Server started listening at port ${getPort()}`) + }); + + createTestUserForDevelopment(); + setUpHealthEndpoint(server); + + server.on('close', async () => { + await DatabaseService.closeDatabase(); + }) + + return server; } -createTestUserForDevelopment() +export default main(); \ No newline at end of file diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 224979c88..1dccb03d0 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -1,5 +1,5 @@ -import request from '../config/request'; import * as Sentry from '@sentry/node'; +import request from '../config/request'; import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -15,18 +15,18 @@ import { 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, + getSiteURL, + getClientIdAzure, + getClientSecretAzure, + getClientSecretHeroku, + getClientIdVercel, + getClientSecretVercel, + getClientIdNetlify, + getClientSecretNetlify, + getClientIdGitHub, + getClientSecretGitHub, + getClientIdGitLab, + getClientSecretGitLab } from '../config'; interface ExchangeCodeAzureResponse { @@ -159,9 +159,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: getClientIdAzure(), + client_secret: getClientSecretAzure(), + redirect_uri: `${getSiteURL()}/integrations/azure-key-vault/oauth2/callback` } as any) )).data; @@ -204,7 +204,7 @@ const exchangeCodeHeroku = async ({ new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_secret: CLIENT_SECRET_HEROKU + client_secret: getClientSecretHeroku() } as any) )).data; @@ -242,9 +242,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: getClientIdVercel(), + client_secret: getClientSecretVercel(), + redirect_uri: `${getSiteURL()}/integrations/vercel/oauth2/callback` } as any) ) ).data; @@ -282,9 +282,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: getClientIdNetlify(), + client_secret: getClientSecretNetlify(), + redirect_uri: `${getSiteURL()}/integrations/netlify/oauth2/callback` } as any) ) ).data; @@ -333,10 +333,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: getClientIdGitHub(), + client_secret: getClientSecretGitHub(), code: code, - redirect_uri: `${SITE_URL}/integrations/github/oauth2/callback` + redirect_uri: `${getSiteURL()}/integrations/github/oauth2/callback` }, headers: { 'Accept': 'application/json', @@ -379,9 +379,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: 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 a3aec1ebd..a0aea080e 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -1,5 +1,5 @@ -import request from '../config/request'; import * as Sentry from '@sentry/node'; +import request from '../config/request'; import { IIntegrationAuth } from '../models'; @@ -8,14 +8,6 @@ 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 { 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: CLIENT_ID_AZURE, + client_id: getClientIdAzure(), scope: 'openid offline_access', refresh_token: refreshToken, grant_type: 'refresh_token', - client_secret: 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: 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: CLIENT_ID_GITLAB, - client_secret: CLIENT_SECRET_GITLAB, - redirect_uri: `${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 653c0df2e..202a38e83 100644 --- a/backend/src/middleware/requestErrorHandler.ts +++ b/backend/src/middleware/requestErrorHandler.ts @@ -1,16 +1,13 @@ -import { ErrorRequestHandler } from "express"; - import * as Sentry from '@sentry/node'; -import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; +import { ErrorRequestHandler } from "express"; +import { InternalServerError } from "../utils/errors"; import { getLogger } from "../utils/logger"; import RequestError, { LogLevel } from "../utils/requestError"; -import { NODE_ENV } from "../config"; - -import { TokenExpiredError } from 'jsonwebtoken'; +import { getNodeEnv } from '../config'; export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => { if (res.headersSent) return next(); - if (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 8fb914258..7fb38ca25 100644 --- a/backend/src/middleware/requireMfaAuth.ts +++ b/backend/src/middleware/requireMfaAuth.ts @@ -1,8 +1,8 @@ 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'; +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, 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 904f4d38e..106ca9bbb 100644 --- a/backend/src/middleware/requireServiceTokenAuth.ts +++ b/backend/src/middleware/requireServiceTokenAuth.ts @@ -1,8 +1,8 @@ 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'; +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, 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 3318bd8d3..19e6b3146 100644 --- a/backend/src/middleware/requireSignupAuth.ts +++ b/backend/src/middleware/requireSignupAuth.ts @@ -1,8 +1,8 @@ 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'; +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, JWT_SIGNUP_SECRET) + jwt.verify(AUTH_TOKEN_VALUE, getJwtSignupSecret()) ); 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/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 diff --git a/backend/src/services/PostHogClient.ts b/backend/src/services/PostHogClient.ts index 0d91a1c13..15ccf0919 100644 --- a/backend/src/services/PostHogClient.ts +++ b/backend/src/services/PostHogClient.ts @@ -1,27 +1,44 @@ import { PostHog } from 'posthog-node'; -import { - NODE_ENV, - POSTHOG_HOST, - POSTHOG_PROJECT_API_KEY, - TELEMETRY_ENABLED -} from '../config'; import { getLogger } from '../utils/logger'; +import { + getNodeEnv, + getTelemetryEnabled, + getPostHogProjectApiKey, + getPostHogHost +} from '../config'; -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 = () => { + if(!getTelemetryEnabled()){ + 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; + if (getNodeEnv() === 'production' && getTelemetryEnabled()) { + // case: enable opt-out telemetry in production + postHogClient = new PostHog(getPostHogProjectApiKey(), { + host: getPostHogHost() + }); + } + + 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..7a4ebf00b 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -1,11 +1,4 @@ import nodemailer from 'nodemailer'; -import { - SMTP_HOST, - SMTP_PORT, - SMTP_USERNAME, - SMTP_PASSWORD, - SMTP_SECURE -} from '../config'; import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN, @@ -14,55 +7,62 @@ import { } from '../variables'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; import * as Sentry from '@sentry/node'; +import { + getSmtpHost, + getSmtpUsername, + getSmtpPassword, + getSmtpSecure, + getSmtpPort +} from '../config'; -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: getSmtpHost(), + port: getSmtpPort() }; -} -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 (getSmtpUsername() && getSmtpPassword()) { + mailOpts.auth = { + user: getSmtpUsername(), + pass: getSmtpPassword() + }; + } + + if (getSmtpSecure() ? getSmtpSecure() : false) { + switch (getSmtpHost()) { + 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 (getSmtpHost().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 +73,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 ${getSmtpHost()}:${getSmtpPort()} \n\t${err}` ); }); diff --git a/backend/src/utils/addDevelopmentUser.ts b/backend/src/utils/addDevelopmentUser.ts index 2020cf3ad..585740a6b 100644 --- a/backend/src/utils/addDevelopmentUser.ts +++ b/backend/src/utils/addDevelopmentUser.ts @@ -4,12 +4,12 @@ * ************************************************************************************************/ -import { NODE_ENV } from "../config" import { Key, Membership, MembershipOrg, Organization, User, Workspace } from "../models"; import { Types } from 'mongoose'; +import { getNodeEnv } from '../config'; export const createTestUserForDevelopment = async () => { - if (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 64c65ea49..ed29c97ca 100644 --- a/backend/src/utils/logger.ts +++ b/backend/src/utils/logger.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { createLogger, format, transports } from 'winston'; import LokiTransport from 'winston-loki'; -import { LOKI_HOST, NODE_ENV } from '../config'; +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(LOKI_HOST !== undefined){ + if(getLokiHost() !== undefined){ _transports.push( new LokiTransport({ - host: LOKI_HOST, + host: getLokiHost(), 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: 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 da2803da7..4b5635bac 100644 --- a/backend/src/utils/requestError.ts +++ b/backend/src/utils/requestError.ts @@ -1,5 +1,5 @@ import { Request } from 'express' -import { VERBOSE_ERROR_OUTPUT } from '../config' +import { getVerboseErrorOutput } from '../config'; export enum LogLevel { DEBUG = 100, @@ -87,7 +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 - if(!VERBOSE_ERROR_OUTPUT){ + if(!getVerboseErrorOutput()){ _context = this._omit(_context, [ 'stacktrace', 'exception', 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..52bfcc614 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -1,13 +1,11 @@ import { - CLIENT_ID_AZURE, - CLIENT_ID_GITLAB + getClientIdHeroku, + getClientSlugVercel, + getClientIdNetlify, + getClientIdAzure, + getClientIdGitLab, + getClientIdGitHub } from '../config'; -import { - CLIENT_ID_HEROKU, - CLIENT_ID_NETLIFY, - CLIENT_ID_GITHUB, - CLIENT_SLUG_VERCEL -} from "../config"; // integrations const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault'; @@ -48,7 +46,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 +56,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: getClientIdHeroku(), + docsLink: '' + }, + { + name: 'Vercel', + slug: 'vercel', + image: 'Vercel.png', + isAvailable: true, + type: 'oauth', + clientId: '', + clientSlug: getClientSlugVercel(), + docsLink: '' + }, + { + name: 'Netlify', + slug: 'netlify', + image: 'Netlify.png', + isAvailable: true, + type: 'oauth', + clientId: getClientIdNetlify(), + docsLink: '' + }, + { + name: 'GitHub', + slug: 'github', + image: 'GitHub.png', + isAvailable: true, + type: 'oauth', + clientId: getClientIdGitHub(), + 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: getClientIdAzure(), + 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: getClientIdGitLab(), + 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/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 2708f9a2b..000000000 --- a/docs/sdk/overview/usage.mdx +++ /dev/null @@ -1,104 +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` | -| `defaultValues` | Default values for secrets if they aren't fetched/passed in | `{}` | - -## Access a Secret Value - -```js -const dbURL = infisical.getSecretValue("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.getSecret("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..58002392f --- /dev/null +++ b/docs/sdks/overview/node.mdx @@ -0,0 +1,154 @@ +--- +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-node` to your project. + +```bash +npm install infisical-node --save +``` + +## Initialization + +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 + 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); + }) + ```` + + + + +## Usage + +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"); +``` + +## 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. 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` | 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",