Add eslint rule and fix as many issues as possible

This commit is contained in:
Khoa Le
2023-06-19 23:42:04 -04:00
parent e9eacc445d
commit 9d33e4756b
689 changed files with 9446 additions and 21821 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"workbench.editor.wrapTabs": true
}
+11 -2
View File
@@ -1,12 +1,21 @@
{ {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"], "plugins": ["@typescript-eslint", "unused-imports"],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended" "plugin:@typescript-eslint/recommended"
], ],
"rules": { "rules": {
"no-console": 2 "no-console": 2,
"quotes": ["error", "double", { "avoidEscape": true }],
"comma-dangle": ["error", "only-multiline"],
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
],
"sort-imports": ["error", { "ignoreDeclarationSort": true }]
} }
} }
+7
View File
@@ -0,0 +1,7 @@
{
"singleQuote": false,
"printWidth": 100,
"trailingComma": "none",
"tabWidth": 2,
"semi": true
}
+1 -1
View File
@@ -14,7 +14,7 @@ declare global {
JWT_SIGNUP_LIFETIME: string; JWT_SIGNUP_LIFETIME: string;
JWT_SIGNUP_SECRET: string; JWT_SIGNUP_SECRET: string;
MONGO_URL: string; MONGO_URL: string;
NODE_ENV: 'development' | 'staging' | 'testing' | 'production'; NODE_ENV: "development" | "staging" | "testing" | "production";
VERBOSE_ERROR_OUTPUT: string; VERBOSE_ERROR_OUTPUT: string;
LOKI_HOST: string; LOKI_HOST: string;
CLIENT_ID_HEROKU: string; CLIENT_ID_HEROKU: string;
+7 -7
View File
@@ -1,9 +1,9 @@
export default { export default {
preset: 'ts-jest', preset: "ts-jest",
testEnvironment: 'node', testEnvironment: "node",
collectCoverageFrom: ['src/*.{js,ts}', '!**/node_modules/**'], collectCoverageFrom: ["src/*.{js,ts}", "!**/node_modules/**"],
modulePaths: ['<rootDir>/src'], modulePaths: ["<rootDir>/src"],
testMatch: ['<rootDir>/tests/**/*.test.ts'], testMatch: ["<rootDir>/tests/**/*.test.ts"],
setupFiles: ['<rootDir>/test-resources/env-vars.js'], setupFiles: ["<rootDir>/test-resources/env-vars.js"],
setupFilesAfterEnv: ['<rootDir>/tests/setupTests.ts'] setupFilesAfterEnv: ["<rootDir>/tests/setupTests.ts"],
}; };
+42 -12479
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -99,6 +99,7 @@
"@typescript-eslint/parser": "^5.40.1", "@typescript-eslint/parser": "^5.40.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.26.0", "eslint": "^8.26.0",
"eslint-plugin-unused-imports": "^2.0.0",
"install": "^0.13.0", "install": "^0.13.0",
"jest": "^29.3.1", "jest": "^29.3.1",
"jest-junit": "^15.0.0", "jest-junit": "^15.0.0",
+66 -66
View File
@@ -1,93 +1,93 @@
import InfisicalClient from 'infisical-node'; import InfisicalClient from "infisical-node";
export const client = new InfisicalClient({ export const client = new InfisicalClient({
token: process.env.INFISICAL_TOKEN! token: process.env.INFISICAL_TOKEN!,
}); });
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000; export const getPort = async () => (await client.getSecret("PORT")).secretValue || 4000;
export const getEncryptionKey = async () => { export const getEncryptionKey = async () => {
const secretValue = (await client.getSecret('ENCRYPTION_KEY')).secretValue; const secretValue = (await client.getSecret("ENCRYPTION_KEY")).secretValue;
return secretValue === '' ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} }
export const getRootEncryptionKey = async () => { export const getRootEncryptionKey = async () => {
const secretValue = (await client.getSecret('ROOT_ENCRYPTION_KEY')).secretValue; const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
return secretValue === '' ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} }
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue === 'true' export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10; export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10;
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d'; export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d";
export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue; export const getJwtAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue;
export const getJwtMfaLifetime = async () => (await client.getSecret('JWT_MFA_LIFETIME')).secretValue || '5m'; export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtMfaSecret = async () => (await client.getSecret('JWT_MFA_LIFETIME')).secretValue || '5m'; export const getJwtMfaSecret = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtRefreshLifetime = async () => (await client.getSecret('JWT_REFRESH_LIFETIME')).secretValue || '90d'; export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d";
export const getJwtRefreshSecret = async () => (await client.getSecret('JWT_REFRESH_SECRET')).secretValue; export const getJwtRefreshSecret = async () => (await client.getSecret("JWT_REFRESH_SECRET")).secretValue;
export const getJwtServiceSecret = async () => (await client.getSecret('JWT_SERVICE_SECRET')).secretValue; export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue;
export const getJwtSignupLifetime = async () => (await client.getSecret('JWT_SIGNUP_LIFETIME')).secretValue || '15m'; export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
export const getJwtProviderAuthSecret = async () => (await client.getSecret('JWT_PROVIDER_AUTH_SECRET')).secretValue; export const getJwtProviderAuthSecret = async () => (await client.getSecret("JWT_PROVIDER_AUTH_SECRET")).secretValue;
export const getJwtProviderAuthLifetime = async () => (await client.getSecret('JWT_PROVIDER_AUTH_LIFETIME')).secretValue || '15m'; export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
export const getJwtSignupSecret = async () => (await client.getSecret('JWT_SIGNUP_SECRET')).secretValue; export const getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_SECRET")).secretValue;
export const getMongoURL = async () => (await client.getSecret('MONGO_URL')).secretValue; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
export const getNodeEnv = async () => (await client.getSecret('NODE_ENV')).secretValue || 'production'; export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
export const getVerboseErrorOutput = async () => (await client.getSecret('VERBOSE_ERROR_OUTPUT')).secretValue === 'true' && true; export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true;
export const getLokiHost = async () => (await client.getSecret('LOKI_HOST')).secretValue; export const getLokiHost = async () => (await client.getSecret("LOKI_HOST")).secretValue;
export const getClientIdAzure = async () => (await client.getSecret('CLIENT_ID_AZURE')).secretValue; export const getClientIdAzure = async () => (await client.getSecret("CLIENT_ID_AZURE")).secretValue;
export const getClientIdHeroku = async () => (await client.getSecret('CLIENT_ID_HEROKU')).secretValue; export const getClientIdHeroku = async () => (await client.getSecret("CLIENT_ID_HEROKU")).secretValue;
export const getClientIdVercel = async () => (await client.getSecret('CLIENT_ID_VERCEL')).secretValue; export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_VERCEL")).secretValue;
export const getClientIdNetlify = async () => (await client.getSecret('CLIENT_ID_NETLIFY')).secretValue; export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue;
export const getClientIdGitHub = async () => (await client.getSecret('CLIENT_ID_GITHUB')).secretValue; export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
export const getClientIdGitLab = async () => (await client.getSecret('CLIENT_ID_GITLAB')).secretValue; export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
export const getClientIdGoogle = async () => (await client.getSecret('CLIENT_ID_GOOGLE')).secretValue; export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
export const getClientSecretAzure = async () => (await client.getSecret('CLIENT_SECRET_AZURE')).secretValue; export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
export const getClientSecretHeroku = async () => (await client.getSecret('CLIENT_SECRET_HEROKU')).secretValue; export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
export const getClientSecretVercel = async () => (await client.getSecret('CLIENT_SECRET_VERCEL')).secretValue; export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
export const getClientSecretNetlify = async () => (await client.getSecret('CLIENT_SECRET_NETLIFY')).secretValue; export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue;
export const getClientSecretGitHub = async () => (await client.getSecret('CLIENT_SECRET_GITHUB')).secretValue; export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
export const getClientSecretGitLab = async () => (await client.getSecret('CLIENT_SECRET_GITLAB')).secretValue; export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
export const getClientSecretGoogle = async () => (await client.getSecret('CLIENT_SECRET_GOOGLE')).secretValue; export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
export const getClientSlugVercel = async () => (await client.getSecret('CLIENT_SLUG_VERCEL')).secretValue; export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
export const getPostHogHost = async () => (await client.getSecret('POSTHOG_HOST')).secretValue || 'https://app.posthog.com'; export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
export const getPostHogProjectApiKey = async () => (await client.getSecret('POSTHOG_PROJECT_API_KEY')).secretValue || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
export const getSentryDSN = async () => (await client.getSecret('SENTRY_DSN')).secretValue; export const getSentryDSN = async () => (await client.getSecret("SENTRY_DSN")).secretValue;
export const getSiteURL = async () => (await client.getSecret('SITE_URL')).secretValue; export const getSiteURL = async () => (await client.getSecret("SITE_URL")).secretValue;
export const getSmtpHost = async () => (await client.getSecret('SMTP_HOST')).secretValue; export const getSmtpHost = async () => (await client.getSecret("SMTP_HOST")).secretValue;
export const getSmtpSecure = async () => (await client.getSecret('SMTP_SECURE')).secretValue === 'true' || false; export const getSmtpSecure = async () => (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false;
export const getSmtpPort = async () => parseInt((await client.getSecret('SMTP_PORT')).secretValue) || 587; export const getSmtpPort = async () => parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587;
export const getSmtpUsername = async () => (await client.getSecret('SMTP_USERNAME')).secretValue; export const getSmtpUsername = async () => (await client.getSecret("SMTP_USERNAME")).secretValue;
export const getSmtpPassword = async () => (await client.getSecret('SMTP_PASSWORD')).secretValue; export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWORD")).secretValue;
export const getSmtpFromAddress = async () => (await client.getSecret('SMTP_FROM_ADDRESS')).secretValue; export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
export const getSmtpFromName = async () => (await client.getSecret('SMTP_FROM_NAME')).secretValue || 'Infisical'; export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
export const getLicenseKey = async () => { export const getLicenseKey = async () => {
const secretValue = (await client.getSecret('LICENSE_KEY')).secretValue; const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
return secretValue === '' ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} }
export const getLicenseServerKey = async () => { export const getLicenseServerKey = async () => {
const secretValue = (await client.getSecret('LICENSE_SERVER_KEY')).secretValue; const secretValue = (await client.getSecret("LICENSE_SERVER_KEY")).secretValue;
return secretValue === '' ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} }
export const getLicenseServerUrl = async () => (await client.getSecret('LICENSE_SERVER_URL')).secretValue || 'https://portal.infisical.com'; export const getLicenseServerUrl = async () => (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com";
// TODO: deprecate from here // TODO: deprecate from here
export const getStripeProductStarter = async () => (await client.getSecret('STRIPE_PRODUCT_STARTER')).secretValue; export const getStripeProductStarter = async () => (await client.getSecret("STRIPE_PRODUCT_STARTER")).secretValue;
export const getStripeProductPro = async () => (await client.getSecret('STRIPE_PRODUCT_PRO')).secretValue; export const getStripeProductPro = async () => (await client.getSecret("STRIPE_PRODUCT_PRO")).secretValue;
export const getStripeProductTeam = async () => (await client.getSecret('STRIPE_PRODUCT_TEAM')).secretValue; export const getStripeProductTeam = async () => (await client.getSecret("STRIPE_PRODUCT_TEAM")).secretValue;
export const getStripePublishableKey = async () => (await client.getSecret('STRIPE_PUBLISHABLE_KEY')).secretValue; export const getStripePublishableKey = async () => (await client.getSecret("STRIPE_PUBLISHABLE_KEY")).secretValue;
export const getStripeSecretKey = async () => (await client.getSecret('STRIPE_SECRET_KEY')).secretValue; export const getStripeSecretKey = async () => (await client.getSecret("STRIPE_SECRET_KEY")).secretValue;
export const getStripeWebhookSecret = async () => (await client.getSecret('STRIPE_WEBHOOK_SECRET')).secretValue; export const getStripeWebhookSecret = async () => (await client.getSecret("STRIPE_WEBHOOK_SECRET")).secretValue;
export const getTelemetryEnabled = async () => (await client.getSecret('TELEMETRY_ENABLED')).secretValue !== 'false' && true; export const getTelemetryEnabled = async () => (await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true;
export const getLoopsApiKey = async () => (await client.getSecret('LOOPS_API_KEY')).secretValue; export const getLoopsApiKey = async () => (await client.getSecret("LOOPS_API_KEY")).secretValue;
export const getSmtpConfigured = async () => (await client.getSecret('SMTP_HOST')).secretValue == '' || (await client.getSecret('SMTP_HOST')).secretValue == undefined ? false : true export const getSmtpConfigured = async () => (await client.getSecret("SMTP_HOST")).secretValue == "" || (await client.getSecret("SMTP_HOST")).secretValue == undefined ? false : true
export const getHttpsEnabled = async () => { export const getHttpsEnabled = async () => {
if ((await getNodeEnv()) != "production") { if ((await getNodeEnv()) != "production") {
// no https for anything other than prod // no https for anything other than prod
return false return false
} }
if ((await client.getSecret('HTTPS_ENABLED')).secretValue == undefined || (await client.getSecret('HTTPS_ENABLED')).secretValue == "") { if ((await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || (await client.getSecret("HTTPS_ENABLED")).secretValue == "") {
// default when no value present // default when no value present
return true return true
} }
return (await client.getSecret('HTTPS_ENABLED')).secretValue === 'true' && true return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true
} }
+14 -14
View File
@@ -1,16 +1,16 @@
import axios from 'axios'; import axios from "axios";
import axiosRetry from 'axios-retry'; import axiosRetry from "axios-retry";
import { import {
getLicenseServerKeyAuthToken,
setLicenseServerKeyAuthToken,
getLicenseKeyAuthToken, getLicenseKeyAuthToken,
setLicenseKeyAuthToken getLicenseServerKeyAuthToken,
} from './storage'; setLicenseKeyAuthToken,
setLicenseServerKeyAuthToken,
} from "./storage";
import { import {
getLicenseKey, getLicenseKey,
getLicenseServerKey, getLicenseServerKey,
getLicenseServerUrl getLicenseServerUrl,
} from './index'; } from "./index";
// should have JWT to interact with the license server // should have JWT to interact with the license server
export const licenseServerKeyRequest = axios.create(); export const licenseServerKeyRequest = axios.create();
@@ -35,8 +35,8 @@ export const refreshLicenseServerKeyToken = async () => {
`${licenseServerUrl}/api/auth/v1/license-server-login`, {}, `${licenseServerUrl}/api/auth/v1/license-server-login`, {},
{ {
headers: { headers: {
'X-API-KEY': licenseServerKey "X-API-KEY": licenseServerKey,
} },
} }
); );
@@ -53,8 +53,8 @@ export const refreshLicenseKeyToken = async () => {
`${licenseServerUrl}/api/auth/v1/license-login`, {}, `${licenseServerUrl}/api/auth/v1/license-login`, {},
{ {
headers: { headers: {
'X-API-KEY': licenseKey "X-API-KEY": licenseKey,
} },
} }
); );
@@ -86,7 +86,7 @@ licenseServerKeyRequest.interceptors.response.use((response) => {
// refresh // refresh
const token = await refreshLicenseServerKeyToken(); const token = await refreshLicenseServerKeyToken();
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token; axios.defaults.headers.common["Authorization"] = "Bearer " + token;
return licenseServerKeyRequest(originalRequest); return licenseServerKeyRequest(originalRequest);
} }
@@ -116,7 +116,7 @@ licenseKeyRequest.interceptors.response.use((response) => {
// refresh // refresh
const token = await refreshLicenseKeyToken(); const token = await refreshLicenseKeyToken();
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token; axios.defaults.headers.common["Authorization"] = "Bearer " + token;
return licenseKeyRequest(originalRequest); return licenseKeyRequest(originalRequest);
} }
+2 -2
View File
@@ -5,7 +5,7 @@ const MemoryLicenseServerKeyTokenStorage = () => {
setToken: (token: string) => { setToken: (token: string) => {
authToken = token; authToken = token;
}, },
getToken: () => authToken getToken: () => authToken,
}; };
}; };
@@ -16,7 +16,7 @@ const MemoryLicenseKeyTokenStorage = () => {
setToken: (token: string) => { setToken: (token: string) => {
authToken = token; authToken = token;
}, },
getToken: () => authToken getToken: () => authToken,
}; };
}; };
+68 -68
View File
@@ -1,36 +1,36 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import jwt from 'jsonwebtoken'; import jwt from "jsonwebtoken";
import * as bigintConversion from 'bigint-conversion'; import * as bigintConversion from "bigint-conversion";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require('jsrp'); const jsrp = require("jsrp");
import { import {
User,
LoginSRPDetail, LoginSRPDetail,
TokenVersion TokenVersion,
} from '../../models'; User,
import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth'; } from "../../models";
import { checkUserDevice } from '../../helpers/user'; import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import { import {
ACTION_LOGIN, ACTION_LOGIN,
ACTION_LOGOUT, ACTION_LOGOUT,
AUTH_MODE_JWT AUTH_MODE_JWT,
} from '../../variables'; } from "../../variables";
import { import {
BadRequestError, BadRequestError,
UnauthorizedRequestError UnauthorizedRequestError,
} from '../../utils/errors'; } from "../../utils/errors";
import { EELogService } from '../../ee/services'; import { EELogService } from "../../ee/services";
import { getChannelFromUserAgent } from '../../utils/posthog'; import { getChannelFromUserAgent } from "../../utils/posthog";
import { import {
getJwtRefreshSecret, getHttpsEnabled,
getJwtAuthLifetime, getJwtAuthLifetime,
getJwtAuthSecret, getJwtAuthSecret,
getHttpsEnabled getJwtRefreshSecret,
} from '../../config'; } from "../../config";
declare module 'jsonwebtoken' { declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string; userId: string;
refreshVersion?: number; refreshVersion?: number;
@@ -46,20 +46,20 @@ declare module 'jsonwebtoken' {
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { const {
email, email,
clientPublicKey clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body; }: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email email,
}).select('+salt +verifier'); }).select("+salt +verifier");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const server = new jsrp.server(); const server = new jsrp.server();
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier verifier: user.verifier,
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
@@ -73,7 +73,7 @@ export const login1 = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt salt: user.salt,
}); });
} }
); );
@@ -89,10 +89,10 @@ export const login1 = async (req: Request, res: Response) => {
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email email,
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag'); }).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email }) const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
@@ -105,7 +105,7 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt b: loginSRPDetailFromDB.serverBInt,
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey); server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
@@ -117,33 +117,33 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id userId: user._id,
}); });
loginAction && await EELogService.createLog({ loginAction && await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP ipAddress: req.realIP,
}); });
// return (access) token in response // return (access) token in response
@@ -152,12 +152,12 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag tag: user.tag,
}); });
} }
return res.status(400).send({ return res.status(400).send({
message: 'Failed to authenticate. Try again?' message: "Failed to authenticate. Try again?",
}); });
} }
); );
@@ -175,51 +175,51 @@ export const logout = async (req: Request, res: Response) => {
} }
// clear httpOnly cookie // clear httpOnly cookie
res.cookie('jid', '', { res.cookie("jid", "", {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean secure: (await getHttpsEnabled()) as boolean,
}); });
const logoutAction = await EELogService.createAction({ const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT, name: ACTION_LOGOUT,
userId: req.user._id userId: req.user._id,
}); });
logoutAction && await EELogService.createLog({ logoutAction && await EELogService.createLog({
userId: req.user._id, userId: req.user._id,
actions: [logoutAction], actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP ipAddress: req.realIP,
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully logged out.' message: "Successfully logged out.",
}); });
}; };
export const getCommonPasswords = async (req: Request, res: Response) => { export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs.readFileSync( const commonPasswords = fs.readFileSync(
path.resolve(__dirname, '../../data/' + 'common_passwords.txt'), path.resolve(__dirname, "../../data/" + "common_passwords.txt"),
'utf8' "utf8"
).split('\n'); ).split("\n");
return res.status(200).send(commonPasswords); return res.status(200).send(commonPasswords);
} }
export const revokeAllSessions = async (req: Request, res: Response) => { export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({ await TokenVersion.updateMany({
user: req.user._id user: req.user._id,
}, { }, {
$inc: { $inc: {
refreshVersion: 1, refreshVersion: 1,
accessVersion: 1 accessVersion: 1,
} },
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully revoked all sessions.' message: "Successfully revoked all sessions.",
}); });
} }
@@ -231,7 +231,7 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
*/ */
export const checkAuth = async (req: Request, res: Response) => { export const checkAuth = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
message: 'Authenticated' message: "Authenticated",
}); });
} }
@@ -245,7 +245,7 @@ export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid; const refreshToken = req.cookies.jid;
if (!refreshToken) { if (!refreshToken) {
throw new Error('Failed to find refresh token in request cookies'); throw new Error("Failed to find refresh token in request cookies");
} }
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
@@ -253,35 +253,35 @@ export const getNewToken = async (req: Request, res: Response) => {
); );
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId _id: decodedToken.userId,
}).select('+publicKey +refreshVersion +accessVersion'); }).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error('Failed to authenticate unfound user'); if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey) if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account'); throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId); const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({ if (!tokenVersion) throw UnauthorizedRequestError({
message: 'Failed to validate refresh token' message: "Failed to validate refresh token",
}); });
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({ if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
message: 'Failed to validate refresh token' message: "Failed to validate refresh token",
}); });
const token = createToken({ const token = createToken({
payload: { payload: {
userId: decodedToken.userId, userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion accessVersion: tokenVersion.refreshVersion,
}, },
expiresIn: await getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret() secret: await getJwtAuthSecret(),
}); });
return res.status(200).send({ return res.status(200).send({
token token,
}); });
}; };
+19 -19
View File
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { Bot, BotKey } from '../../models'; import { Bot, BotKey } from "../../models";
import { createBot } from '../../helpers/bot'; import { createBot } from "../../helpers/bot";
interface BotKey { interface BotKey {
encryptedKey: string; encryptedKey: string;
@@ -19,20 +19,20 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
let bot = await Bot.findOne({ let bot = await Bot.findOne({
workspace: workspaceId workspace: workspaceId,
}); });
if (!bot) { if (!bot) {
// case: bot doesn't exist for workspace with id [workspaceId] // case: bot doesn't exist for workspace with id [workspaceId]
// -> create a new bot and return it // -> create a new bot and return it
bot = await createBot({ bot = await createBot({
name: 'Infisical Bot', name: "Infisical Bot",
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
} }
return res.status(200).send({ return res.status(200).send({
bot bot,
}); });
}; };
@@ -49,40 +49,40 @@ export const setBotActiveState = async (req: Request, res: Response) => {
// bot state set to active -> share workspace key with bot // bot state set to active -> share workspace key with bot
if (!botKey?.encryptedKey || !botKey?.nonce) { if (!botKey?.encryptedKey || !botKey?.nonce) {
return res.status(400).send({ return res.status(400).send({
message: 'Failed to set bot state to active - missing bot key' message: "Failed to set bot state to active - missing bot key",
}); });
} }
await BotKey.findOneAndUpdate({ await BotKey.findOneAndUpdate({
workspace: req.bot.workspace workspace: req.bot.workspace,
}, { }, {
encryptedKey: botKey.encryptedKey, encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce, nonce: botKey.nonce,
sender: req.user._id, sender: req.user._id,
bot: req.bot._id, bot: req.bot._id,
workspace: req.bot.workspace workspace: req.bot.workspace,
}, { }, {
upsert: true, upsert: true,
new: true new: true,
}); });
} else { } else {
// case: bot state set to inactive -> delete bot's workspace key // case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({ await BotKey.deleteOne({
bot: req.bot._id bot: req.bot._id,
}); });
} }
let bot = await Bot.findOneAndUpdate({ const bot = await Bot.findOneAndUpdate({
_id: req.bot._id _id: req.bot._id,
}, { }, {
isActive isActive,
}, { }, {
new: true new: true,
}); });
if (!bot) throw new Error('Failed to update bot active state'); if (!bot) throw new Error("Failed to update bot active state");
return res.status(200).send({ return res.status(200).send({
bot bot,
}); });
}; };
+17 -17
View File
@@ -1,19 +1,19 @@
import * as authController from './authController'; import * as authController from "./authController";
import * as botController from './botController'; import * as botController from "./botController";
import * as integrationAuthController from './integrationAuthController'; import * as integrationAuthController from "./integrationAuthController";
import * as integrationController from './integrationController'; import * as integrationController from "./integrationController";
import * as keyController from './keyController'; import * as keyController from "./keyController";
import * as membershipController from './membershipController'; import * as membershipController from "./membershipController";
import * as membershipOrgController from './membershipOrgController'; import * as membershipOrgController from "./membershipOrgController";
import * as organizationController from './organizationController'; import * as organizationController from "./organizationController";
import * as passwordController from './passwordController'; import * as passwordController from "./passwordController";
import * as secretController from './secretController'; import * as secretController from "./secretController";
import * as serviceTokenController from './serviceTokenController'; import * as serviceTokenController from "./serviceTokenController";
import * as signupController from './signupController'; import * as signupController from "./signupController";
import * as stripeController from './stripeController'; import * as stripeController from "./stripeController";
import * as userActionController from './userActionController'; import * as userActionController from "./userActionController";
import * as userController from './userController'; import * as userController from "./userController";
import * as workspaceController from './workspaceController'; import * as workspaceController from "./workspaceController";
export { export {
authController, authController,
@@ -31,5 +31,5 @@ export {
stripeController, stripeController,
userActionController, userActionController,
userController, userController,
workspaceController workspaceController,
}; };
@@ -1,21 +1,17 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth } from "../../models";
import { IntegrationService } from "../../services";
import { import {
IntegrationAuth, ALGORITHM_AES_256_GCM,
Bot ENCODING_SCHEME_UTF8,
} from '../../models'; INTEGRATION_RAILWAY_API_URL,
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables'; INTEGRATION_SET,
import { IntegrationService } from '../../services'; INTEGRATION_VERCEL_API_URL,
import { getIntegrationOptions as getIntegrationOptionsFunc
getApps, } from "../../variables";
getTeams,
revokeAccess
} from '../../integrations';
import {
INTEGRATION_VERCEL_API_URL,
INTEGRATION_RAILWAY_API_URL
} from '../../variables';
import { standardRequest } from '../../config/request';
/*** /***
* Return integration authorization with id [integrationAuthId] * Return integration authorization with id [integrationAuthId]
@@ -24,21 +20,22 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params; const { integrationAuthId } = req.params;
const integrationAuth = await IntegrationAuth.findById(integrationAuthId); const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth) return res.status(400).send({ if (!integrationAuth)
message: 'Failed to find integration authorization' return res.status(400).send({
}); message: "Failed to find integration authorization"
});
return res.status(200).send({ return res.status(200).send({
integrationAuth integrationAuth
}); });
} };
export const getIntegrationOptions = async (req: Request, res: Response) => { export const getIntegrationOptions = async (req: Request, res: Response) => {
const INTEGRATION_OPTIONS = await getIntegrationOptionsFunc(); const INTEGRATION_OPTIONS = await getIntegrationOptionsFunc();
return res.status(200).send({ return res.status(200).send({
integrationOptions: INTEGRATION_OPTIONS, integrationOptions: INTEGRATION_OPTIONS
}); });
}; };
/** /**
@@ -47,24 +44,20 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
* @param res * @param res
* @returns * @returns
*/ */
export const oAuthExchange = async ( export const oAuthExchange = async (req: Request, res: Response) => {
req: Request,
res: Response
) => {
const { workspaceId, code, integration } = req.body; const { workspaceId, code, integration } = req.body;
if (!INTEGRATION_SET.has(integration)) if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
throw new Error('Failed to validate integration');
const environments = req.membership.workspace?.environments || []; const environments = req.membership.workspace?.environments || [];
if(environments.length === 0){ if (environments.length === 0) {
throw new Error("Failed to get environments") throw new Error("Failed to get environments");
} }
const integrationAuth = await IntegrationService.handleOAuthExchange({ const integrationAuth = await IntegrationService.handleOAuthExchange({
workspaceId, workspaceId,
integration, integration,
code, code,
environment: environments[0].slug, environment: environments[0].slug
}); });
return res.status(200).send({ return res.status(200).send({
@@ -78,66 +71,67 @@ export const oAuthExchange = async (
* @param req * @param req
* @param res * @param res
*/ */
export const saveIntegrationAccessToken = async ( export const saveIntegrationAccessToken = async (req: Request, res: Response) => {
req: Request, // TODO: refactor
res: Response // TODO: check if access token is valid for each integration
) => {
// TODO: refactor
// TODO: check if access token is valid for each integration
let integrationAuth; let integrationAuth;
const { const {
workspaceId, workspaceId,
accessId, accessId,
accessToken, accessToken,
url, url,
namespace, namespace,
integration integration
}: { }: {
workspaceId: string; workspaceId: string;
accessId: string | null; accessId: string | null;
accessToken: string; accessToken: string;
url: string; url: string;
namespace: string; namespace: string;
integration: string; integration: string;
} = req.body; } = req.body;
const bot = await Bot.findOne({ const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
isActive: true isActive: true
}); });
if (!bot) throw new Error('Bot must be enabled to save integration access token'); if (!bot) throw new Error("Bot must be enabled to save integration access token");
integrationAuth = await IntegrationAuth.findOneAndUpdate({ integrationAuth = await IntegrationAuth.findOneAndUpdate(
workspace: new Types.ObjectId(workspaceId), {
integration workspace: new Types.ObjectId(workspaceId),
}, { integration
workspace: new Types.ObjectId(workspaceId), },
integration, {
url, workspace: new Types.ObjectId(workspaceId),
namespace, integration,
algorithm: ALGORITHM_AES_256_GCM, url,
keyEncoding: ENCODING_SCHEME_UTF8 namespace,
}, { algorithm: ALGORITHM_AES_256_GCM,
new: true, keyEncoding: ENCODING_SCHEME_UTF8
upsert: true },
}); {
new: true,
upsert: true
}
);
// encrypt and save integration access details // encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({ integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(), integrationAuthId: integrationAuth._id.toString(),
accessId, accessId,
accessToken, accessToken,
accessExpiresAt: undefined accessExpiresAt: undefined
}); });
if (!integrationAuth) throw new Error('Failed to save integration access token'); if (!integrationAuth) throw new Error("Failed to save integration access token");
return res.status(200).send({ return res.status(200).send({
integrationAuth integrationAuth
}); });
} };
/** /**
* Return list of applications allowed for integration with integration authorization id [integrationAuthId] * Return list of applications allowed for integration with integration authorization id [integrationAuthId]
@@ -151,13 +145,13 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
const apps = await getApps({ const apps = await getApps({
integrationAuth: req.integrationAuth, integrationAuth: req.integrationAuth,
accessToken: req.accessToken, accessToken: req.accessToken,
accessId: req.accessId, accessId: req.accessId,
...teamId && { teamId } ...(teamId && { teamId })
}); });
return res.status(200).send({ return res.status(200).send({
apps apps
}); });
}; };
/** /**
@@ -167,15 +161,15 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getIntegrationAuthTeams = async (req: Request, res: Response) => { export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
const teams = await getTeams({ const teams = await getTeams({
integrationAuth: req.integrationAuth, integrationAuth: req.integrationAuth,
accessToken: req.accessToken accessToken: req.accessToken
}); });
return res.status(200).send({ return res.status(200).send({
teams teams
}); });
} };
/** /**
* Return list of available Vercel (preview) branches for Vercel project with * Return list of available Vercel (preview) branches for Vercel project with
@@ -184,43 +178,44 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => { export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params; const appId = req.query.appId as string;
const appId = req.query.appId as string;
interface VercelBranch { interface VercelBranch {
ref: string; ref: string;
lastCommit: string; lastCommit: string;
isProtected: boolean; isProtected: boolean;
} }
const params = new URLSearchParams({ const params = new URLSearchParams({
projectId: appId, projectId: appId,
...(req.integrationAuth.teamId ? { ...(req.integrationAuth.teamId
teamId: req.integrationAuth.teamId ? {
} : {}) teamId: req.integrationAuth.teamId
}); }
: {})
});
let branches: string[] = []; let branches: string[] = [];
if (appId && appId !== '') { if (appId && appId !== "") {
const { data }: { data: VercelBranch[] } = await standardRequest.get( const { data }: { data: VercelBranch[] } = await standardRequest.get(
`${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`, `${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`,
{ {
params, params,
headers: { headers: {
Authorization: `Bearer ${req.accessToken}`, Authorization: `Bearer ${req.accessToken}`,
'Accept-Encoding': 'application/json' "Accept-Encoding": "application/json"
} }
} }
); );
branches = data.map((b) => b.ref); branches = data.map((b) => b.ref);
} }
return res.status(200).send({ return res.status(200).send({
branches branches
}); });
} };
/** /**
* Return list of Railway environments for Railway project with * Return list of Railway environments for Railway project with
@@ -229,26 +224,25 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
* @param res * @param res
*/ */
export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => { export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params; const appId = req.query.appId as string;
const appId = req.query.appId as string;
interface RailwayEnvironment { interface RailwayEnvironment {
node: { node: {
id: string; id: string;
name: string; name: string;
isEphemeral: boolean; isEphemeral: boolean;
} };
} }
interface Environment { interface Environment {
environmentId: string; environmentId: string;
name: string; name: string;
} }
let environments: Environment[] = []; let environments: Environment[] = [];
if (appId && appId !== '') { if (appId && appId !== "") {
const query = ` const query = `
query GetEnvironments($projectId: String!, $after: String, $before: String, $first: Int, $isEphemeral: Boolean, $last: Int) { query GetEnvironments($projectId: String!, $after: String, $before: String, $first: Int, $isEphemeral: Boolean, $last: Int) {
environments(projectId: $projectId, after: $after, before: $before, first: $first, isEphemeral: $isEphemeral, last: $last) { environments(projectId: $projectId, after: $after, before: $before, first: $first, isEphemeral: $isEphemeral, last: $last) {
edges { edges {
@@ -262,32 +256,42 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
} }
`; `;
const variables = { const variables = {
projectId: appId projectId: appId
} };
const { data: { data: { environments: { edges } } } } = await standardRequest.post(INTEGRATION_RAILWAY_API_URL, { const {
query, data: {
variables, data: {
}, { environments: { edges }
headers: { }
'Authorization': `Bearer ${req.accessToken}`, }
'Content-Type': 'application/json', } = await standardRequest.post(
}, INTEGRATION_RAILWAY_API_URL,
}); {
query,
variables
},
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Content-Type": "application/json"
}
}
);
environments = edges.map((e: RailwayEnvironment) => { environments = edges.map((e: RailwayEnvironment) => {
return ({ return {
name: e.node.name, name: e.node.name,
environmentId: e.node.id environmentId: e.node.id
}); };
}); });
} }
return res.status(200).send({ return res.status(200).send({
environments environments
}); });
} };
/** /**
* Return list of Railway services for Railway project with id * Return list of Railway services for Railway project with id
@@ -296,24 +300,23 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
* @param res * @param res
*/ */
export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => { export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params; const appId = req.query.appId as string;
const appId = req.query.appId as string;
interface RailwayService { interface RailwayService {
node: { node: {
id: string; id: string;
name: string; name: string;
} };
} }
interface Service { interface Service {
name: string; name: string;
serviceId: string; serviceId: string;
} }
let services: Service[] = []; let services: Service[] = [];
const query = ` const query = `
query project($id: String!) { query project($id: String!) {
project(id: $id) { project(id: $id) {
createdAt createdAt
@@ -341,31 +344,43 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
} }
`; `;
if (appId && appId !== '') { if (appId && appId !== "") {
const variables = { const variables = {
id: appId id: appId
} };
const { data: { data: { project: { services: { edges } } } } } = await standardRequest.post(INTEGRATION_RAILWAY_API_URL, { const {
query, data: {
variables data: {
}, { project: {
headers: { services: { edges }
'Authorization': `Bearer ${req.accessToken}`, }
'Content-Type': 'application/json', }
}, }
}); } = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL,
{
query,
variables
},
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Content-Type": "application/json"
}
}
);
services = edges.map((e: RailwayService) => ({ services = edges.map((e: RailwayService) => ({
name: e.node.name, name: e.node.name,
serviceId: e.node.id serviceId: e.node.id
})); }));
} }
return res.status(200).send({ return res.status(200).send({
services services
}); });
} };
/** /**
* Delete integration authorization with id [integrationAuthId] * Delete integration authorization with id [integrationAuthId]
@@ -376,10 +391,10 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
export const deleteIntegrationAuth = async (req: Request, res: Response) => { export const deleteIntegrationAuth = async (req: Request, res: Response) => {
const integrationAuth = await revokeAccess({ const integrationAuth = await revokeAccess({
integrationAuth: req.integrationAuth, integrationAuth: req.integrationAuth,
accessToken: req.accessToken, accessToken: req.accessToken
}); });
return res.status(200).send({ return res.status(200).send({
integrationAuth, integrationAuth
}); });
}; };
+10 -10
View File
@@ -1,6 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Key } from '../../models'; import { Key } from "../../models";
import { findMembership } from '../../helpers/membership'; import { findMembership } from "../../helpers/membership";
/** /**
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with * Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
@@ -16,11 +16,11 @@ export const uploadKey = async (req: Request, res: Response) => {
// validate membership of receiver // validate membership of receiver
const receiverMembership = await findMembership({ const receiverMembership = await findMembership({
user: key.userId, user: key.userId,
workspace: workspaceId workspace: workspaceId,
}); });
if (!receiverMembership) { if (!receiverMembership) {
throw new Error('Failed receiver membership validation for workspace'); throw new Error("Failed receiver membership validation for workspace");
} }
await new Key({ await new Key({
@@ -28,11 +28,11 @@ export const uploadKey = async (req: Request, res: Response) => {
nonce: key.nonce, nonce: key.nonce,
sender: req.user._id, sender: req.user._id,
receiver: key.userId, receiver: key.userId,
workspace: workspaceId workspace: workspaceId,
}).save(); }).save();
return res.status(200).send({ return res.status(200).send({
message: 'Successfully uploaded key to workspace' message: "Successfully uploaded key to workspace",
}); });
}; };
@@ -48,16 +48,16 @@ export const getLatestKey = async (req: Request, res: Response) => {
// get latest key // get latest key
const latestKey = await Key.find({ const latestKey = await Key.find({
workspace: workspaceId, workspace: workspaceId,
receiver: req.user._id receiver: req.user._id,
}) })
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.limit(1) .limit(1)
.populate('sender', '+publicKey'); .populate("sender", "+publicKey");
const resObj: any = {}; const resObj: any = {};
if (latestKey.length > 0) { if (latestKey.length > 0) {
resObj['latestKey'] = latestKey[0]; resObj["latestKey"] = latestKey[0];
} }
return res.status(200).send(resObj); return res.status(200).send(resObj);
@@ -1,12 +1,9 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Membership, MembershipOrg, User, Key } from '../../models'; import { Key, Membership, MembershipOrg, User } from "../../models";
import { import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
findMembership, import { sendMail } from "../../helpers/nodemailer";
deleteMembership as deleteMember import { ACCEPTED, ADMIN, MEMBER } from "../../variables";
} from '../../helpers/membership'; import { getSiteURL } from "../../config";
import { sendMail } from '../../helpers/nodemailer';
import { ADMIN, MEMBER, ACCEPTED } from '../../variables';
import { getSiteURL } from '../../config';
/** /**
* Check that user is a member of workspace with id [workspaceId] * Check that user is a member of workspace with id [workspaceId]
@@ -23,12 +20,12 @@ export const validateMembership = async (req: Request, res: Response) => {
}); });
if (!membership) { if (!membership) {
throw new Error('Failed to validate membership'); throw new Error("Failed to validate membership");
} }
return res.status(200).send({ return res.status(200).send({
message: 'Workspace membership confirmed' message: "Workspace membership confirmed"
}); });
}; };
/** /**
@@ -43,12 +40,10 @@ export const deleteMembership = async (req: Request, res: Response) => {
// check if membership to delete exists // check if membership to delete exists
const membershipToDelete = await Membership.findOne({ const membershipToDelete = await Membership.findOne({
_id: membershipId _id: membershipId
}).populate('user'); }).populate("user");
if (!membershipToDelete) { if (!membershipToDelete) {
throw new Error( throw new Error("Failed to delete workspace membership that doesn't exist");
"Failed to delete workspace membership that doesn't exist"
);
} }
// check if user is a member and admin of the workspace // check if user is a member and admin of the workspace
@@ -59,12 +54,12 @@ export const deleteMembership = async (req: Request, res: Response) => {
}); });
if (!membership) { if (!membership) {
throw new Error('Failed to validate workspace membership'); throw new Error("Failed to validate workspace membership");
} }
if (membership.role !== ADMIN) { if (membership.role !== ADMIN) {
// user is not an admin member of the workspace // user is not an admin member of the workspace
throw new Error('Insufficient role for deleting workspace membership'); throw new Error("Insufficient role for deleting workspace membership");
} }
// delete workspace membership // delete workspace membership
@@ -72,9 +67,9 @@ export const deleteMembership = async (req: Request, res: Response) => {
membershipId: membershipToDelete._id.toString() membershipId: membershipToDelete._id.toString()
}); });
return res.status(200).send({ return res.status(200).send({
deletedMembership deletedMembership
}); });
}; };
/** /**
@@ -88,7 +83,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
const { role } = req.body; const { role } = req.body;
if (![ADMIN, MEMBER].includes(role)) { if (![ADMIN, MEMBER].includes(role)) {
throw new Error('Failed to validate role'); throw new Error("Failed to validate role");
} }
// validate target membership // validate target membership
@@ -97,7 +92,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
}); });
if (!membershipToChangeRole) { if (!membershipToChangeRole) {
throw new Error('Failed to find membership to change role'); throw new Error("Failed to find membership to change role");
} }
// check if user is a member and admin of target membership's // check if user is a member and admin of target membership's
@@ -108,20 +103,20 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
}); });
if (!membership) { if (!membership) {
throw new Error('Failed to validate membership'); throw new Error("Failed to validate membership");
} }
if (membership.role !== ADMIN) { if (membership.role !== ADMIN) {
// user is not an admin member of the workspace // user is not an admin member of the workspace
throw new Error('Insufficient role for changing member roles'); throw new Error("Insufficient role for changing member roles");
} }
membershipToChangeRole.role = role; membershipToChangeRole.role = role;
await membershipToChangeRole.save(); await membershipToChangeRole.save();
return res.status(200).send({ return res.status(200).send({
membership: membershipToChangeRole membership: membershipToChangeRole
}); });
}; };
/** /**
@@ -136,10 +131,9 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
const invitee = await User.findOne({ const invitee = await User.findOne({
email email
}).select('+publicKey'); }).select("+publicKey");
if (!invitee || !invitee?.publicKey) if (!invitee || !invitee?.publicKey) throw new Error("Failed to validate invitee");
throw new Error('Failed to validate invitee');
// validate invitee's workspace membership - ensure member isn't // validate invitee's workspace membership - ensure member isn't
// already a member of the workspace // already a member of the workspace
@@ -148,8 +142,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
workspace: workspaceId workspace: workspaceId
}); });
if (inviteeMembership) if (inviteeMembership) throw new Error("Failed to add existing member of workspace");
throw new Error('Failed to add existing member of workspace');
// validate invitee's organization membership - ensure that only // validate invitee's organization membership - ensure that only
// (accepted) organization members can be added to the workspace // (accepted) organization members can be added to the workspace
@@ -159,8 +152,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
status: ACCEPTED status: ACCEPTED
}); });
if (!membershipOrg) if (!membershipOrg) throw new Error("Failed to validate invitee's organization membership");
throw new Error("Failed to validate invitee's organization membership");
// get latest key // get latest key
const latestKey = await Key.findOne({ const latestKey = await Key.findOne({
@@ -168,29 +160,29 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
receiver: req.user._id receiver: req.user._id
}) })
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.populate('sender', '+publicKey'); .populate("sender", "+publicKey");
// create new workspace membership // create new workspace membership
const m = await new Membership({ await new Membership({
user: invitee._id, user: invitee._id,
workspace: workspaceId, workspace: workspaceId,
role: MEMBER role: MEMBER
}).save(); }).save();
await sendMail({ await sendMail({
template: 'workspaceInvitation.handlebars', template: "workspaceInvitation.handlebars",
subjectLine: 'Infisical workspace invitation', subjectLine: "Infisical workspace invitation",
recipients: [invitee.email], recipients: [invitee.email],
substitutions: { substitutions: {
inviterFirstName: req.user.firstName, inviterFirstName: req.user.firstName,
inviterEmail: req.user.email, inviterEmail: req.user.email,
workspaceName: req.membership.workspace.name, workspaceName: req.membership.workspace.name,
callback_url: (await getSiteURL()) + '/login' callback_url: (await getSiteURL()) + "/login"
} }
}); });
return res.status(200).send({ return res.status(200).send({
invitee, invitee,
latestKey latestKey
}); });
}; };
@@ -1,15 +1,27 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { MembershipOrg, Organization, User } from '../../models'; import { MembershipOrg, Organization, User } from "../../models";
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg'; import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
import { createToken } from '../../helpers/auth'; import { createToken } from "../../helpers/auth";
import { updateSubscriptionOrgQuantity } from '../../helpers/organization'; import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import { sendMail } from '../../helpers/nodemailer'; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from '../../services'; import { TokenService } from "../../services";
import { EELicenseService } from '../../ee/services'; import { EELicenseService } from "../../ee/services";
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables'; import {
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config'; ACCEPTED,
import { validateUserEmail } from '../../validation'; ADMIN,
INVITED,
MEMBER,
OWNER,
TOKEN_EMAIL_ORG_INVITATION
} from "../../variables";
import {
getJwtSignupLifetime,
getJwtSignupSecret,
getSiteURL,
getSmtpConfigured
} from "../../config";
import { validateUserEmail } from "../../validation";
/** /**
* Delete organization membership with id [membershipOrgId] from organization * Delete organization membership with id [membershipOrgId] from organization
@@ -17,18 +29,16 @@ import { validateUserEmail } from '../../validation';
* @param res * @param res
* @returns * @returns
*/ */
export const deleteMembershipOrg = async (req: Request, res: Response) => { export const deleteMembershipOrg = async (req: Request, _res: Response) => {
const { membershipOrgId } = req.params; const { membershipOrgId } = req.params;
// check if organization membership to delete exists // check if organization membership to delete exists
const membershipOrgToDelete = await MembershipOrg.findOne({ const membershipOrgToDelete = await MembershipOrg.findOne({
_id: membershipOrgId _id: membershipOrgId
}).populate('user'); }).populate("user");
if (!membershipOrgToDelete) { if (!membershipOrgToDelete) {
throw new Error( throw new Error("Failed to delete organization membership that doesn't exist");
"Failed to delete organization membership that doesn't exist"
);
} }
// check if user is a member and admin of the organization // check if user is a member and admin of the organization
@@ -39,16 +49,16 @@ export const deleteMembershipOrg = async (req: Request, res: Response) => {
}); });
if (!membershipOrg) { if (!membershipOrg) {
throw new Error('Failed to validate organization membership'); throw new Error("Failed to validate organization membership");
} }
if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) { if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) {
// user is not an admin member of the organization // user is not an admin member of the organization
throw new Error('Insufficient role for deleting organization membership'); throw new Error("Insufficient role for deleting organization membership");
} }
// delete organization membership // delete organization membership
const deletedMembershipOrg = await deleteMemberFromOrg({ await deleteMemberFromOrg({
membershipOrgId: membershipOrgToDelete._id.toString() membershipOrgId: membershipOrgToDelete._id.toString()
}); });
@@ -56,7 +66,7 @@ export const deleteMembershipOrg = async (req: Request, res: Response) => {
organizationId: membershipOrg.organization.toString() organizationId: membershipOrg.organization.toString()
}); });
return membershipOrgToDelete; return membershipOrgToDelete;
}; };
/** /**
@@ -66,14 +76,14 @@ export const deleteMembershipOrg = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const changeMembershipOrgRole = async (req: Request, res: Response) => { export const changeMembershipOrgRole = async (req: Request, res: Response) => {
// change role for (target) organization membership with id // change role for (target) organization membership with id
// [membershipOrgId] // [membershipOrgId]
let membershipToChangeRole; let membershipToChangeRole;
return res.status(200).send({ return res.status(200).send({
membershipOrg: membershipToChangeRole membershipOrg: membershipToChangeRole
}); });
}; };
/** /**
@@ -84,7 +94,7 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const inviteUserToOrganization = async (req: Request, res: Response) => { export const inviteUserToOrganization = async (req: Request, res: Response) => {
let invitee, inviteeMembershipOrg, completeInviteLink; let inviteeMembershipOrg, completeInviteLink;
const { organizationId, inviteeEmail } = req.body; const { organizationId, inviteeEmail } = req.body;
const host = req.headers.host; const host = req.headers.host;
const siteUrl = `${req.protocol}://${host}`; const siteUrl = `${req.protocol}://${host}`;
@@ -96,7 +106,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
}); });
if (!membershipOrg) { if (!membershipOrg) {
throw new Error('Failed to validate organization membership'); throw new Error("Failed to validate organization membership");
} }
const plan = await EELicenseService.getPlan(organizationId); const plan = await EELicenseService.getPlan(organizationId);
@@ -107,14 +117,15 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
if (plan.membersUsed >= plan.memberLimit) { if (plan.membersUsed >= plan.memberLimit) {
// case: number of members used exceeds the number of members allowed // case: number of members used exceeds the number of members allowed
return res.status(400).send({ return res.status(400).send({
message: 'Failed to invite member due to member limit reached. Upgrade plan to invite more members.' message:
"Failed to invite member due to member limit reached. Upgrade plan to invite more members."
}); });
} }
} }
invitee = await User.findOne({ const invitee = await User.findOne({
email: inviteeEmail email: inviteeEmail
}).select('+publicKey'); }).select("+publicKey");
if (invitee) { if (invitee) {
// case: invitee is an existing user // case: invitee is an existing user
@@ -125,13 +136,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
}); });
if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) { if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) {
throw new Error( throw new Error("Failed to invite an existing member of the organization");
'Failed to invite an existing member of the organization'
);
} }
if (!inviteeMembershipOrg) { if (!inviteeMembershipOrg) {
await new MembershipOrg({ await new MembershipOrg({
user: invitee, user: invitee,
inviteEmail: inviteeEmail, inviteEmail: inviteeEmail,
@@ -165,7 +173,6 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
const organization = await Organization.findOne({ _id: organizationId }); const organization = await Organization.findOne({ _id: organizationId });
if (organization) { if (organization) {
const token = await TokenService.createToken({ const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION, type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail, email: inviteeEmail,
@@ -173,8 +180,8 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
}); });
await sendMail({ await sendMail({
template: 'organizationInvitation.handlebars', template: "organizationInvitation.handlebars",
subjectLine: 'Infisical organization invitation', subjectLine: "Infisical organization invitation",
recipients: [inviteeEmail], recipients: [inviteeEmail],
substitutions: { substitutions: {
inviterFirstName: req.user.firstName, inviterFirstName: req.user.firstName,
@@ -183,21 +190,23 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
email: inviteeEmail, email: inviteeEmail,
organizationId: organization._id.toString(), organizationId: organization._id.toString(),
token, token,
callback_url: (await getSiteURL()) + '/signupinvite' callback_url: (await getSiteURL()) + "/signupinvite"
} }
}); });
if (!(await getSmtpConfigured())) { if (!(await getSmtpConfigured())) {
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}` completeInviteLink = `${
siteUrl + "/signupinvite"
}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`;
} }
} }
await updateSubscriptionOrgQuantity({ organizationId }); await updateSubscriptionOrgQuantity({ organizationId });
return res.status(200).send({ return res.status(200).send({
message: `Sent an invite link to ${req.body.inviteeEmail}`, message: `Sent an invite link to ${req.body.inviteeEmail}`,
completeInviteLink completeInviteLink
}); });
}; };
/** /**
@@ -208,14 +217,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const verifyUserToOrganization = async (req: Request, res: Response) => { export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user; let user;
const { const { email, organizationId, code } = req.body;
email,
organizationId,
code
} = req.body;
user = await User.findOne({ email }).select('+publicKey'); user = await User.findOne({ email }).select("+publicKey");
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email, inviteEmail: email,
@@ -223,8 +228,7 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
organization: new Types.ObjectId(organizationId) organization: new Types.ObjectId(organizationId)
}); });
if (!membershipOrg) if (!membershipOrg) throw new Error("Failed to find any invitations for email");
throw new Error('Failed to find any invitations for email');
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_ORG_INVITATION, type: TOKEN_EMAIL_ORG_INVITATION,
@@ -244,8 +248,8 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully verified email', message: "Successfully verified email",
user, user
}); });
} }
@@ -265,9 +269,9 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
secret: await getJwtSignupSecret() secret: await getJwtSignupSecret()
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully verified email', message: "Successfully verified email",
user, user,
token token
}); });
}; };
@@ -1,28 +1,27 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import Stripe from 'stripe'; import Stripe from "stripe";
import { import {
IncidentContactOrg,
Membership, Membership,
MembershipOrg, MembershipOrg,
Organization, Organization,
Workspace, Workspace,
IncidentContactOrg } from "../../models";
} from '../../models'; import { createOrganization as create } from "../../helpers/organization";
import { createOrganization as create } from '../../helpers/organization'; import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { addMembershipsOrg } from '../../helpers/membershipOrg'; import { ACCEPTED, OWNER } from "../../variables";
import { OWNER, ACCEPTED } from '../../variables'; import { getSiteURL, getStripeSecretKey } from "../../config";
import _ from 'lodash';
import { getStripeSecretKey, getSiteURL } from '../../config';
export const getOrganizations = async (req: Request, res: Response) => { export const getOrganizations = async (req: Request, res: Response) => {
const organizations = ( const organizations = (
await MembershipOrg.find({ await MembershipOrg.find({
user: req.user._id, user: req.user._id,
status: ACCEPTED status: ACCEPTED,
}).populate('organization') }).populate("organization")
).map((m) => m.organization); ).map((m) => m.organization);
return res.status(200).send({ return res.status(200).send({
organizations organizations,
}); });
}; };
@@ -37,24 +36,24 @@ export const createOrganization = async (req: Request, res: Response) => {
const { organizationName } = req.body; const { organizationName } = req.body;
if (organizationName.length < 1) { if (organizationName.length < 1) {
throw new Error('Organization names must be at least 1-character long'); throw new Error("Organization names must be at least 1-character long");
} }
// create organization and add user as member // create organization and add user as member
const organization = await create({ const organization = await create({
email: req.user.email, email: req.user.email,
name: organizationName name: organizationName,
}); });
await addMembershipsOrg({ await addMembershipsOrg({
userIds: [req.user._id.toString()], userIds: [req.user._id.toString()],
organizationId: organization._id.toString(), organizationId: organization._id.toString(),
roles: [OWNER], roles: [OWNER],
statuses: [ACCEPTED] statuses: [ACCEPTED],
}); });
return res.status(200).send({ return res.status(200).send({
organization organization,
}); });
}; };
@@ -67,7 +66,7 @@ export const createOrganization = async (req: Request, res: Response) => {
export const getOrganization = async (req: Request, res: Response) => { export const getOrganization = async (req: Request, res: Response) => {
const organization = req.organization const organization = req.organization
return res.status(200).send({ return res.status(200).send({
organization organization,
}); });
}; };
@@ -81,11 +80,11 @@ export const getOrganizationMembers = async (req: Request, res: Response) => {
const { organizationId } = req.params; const { organizationId } = req.params;
const users = await MembershipOrg.find({ const users = await MembershipOrg.find({
organization: organizationId organization: organizationId,
}).populate('user', '+publicKey'); }).populate("user", "+publicKey");
return res.status(200).send({ return res.status(200).send({
users users,
}); });
}; };
@@ -105,23 +104,23 @@ export const getOrganizationWorkspaces = async (
( (
await Workspace.find( await Workspace.find(
{ {
organization: organizationId organization: organizationId,
}, },
'_id' "_id"
) )
).map((w) => w._id.toString()) ).map((w) => w._id.toString())
); );
const workspaces = ( const workspaces = (
await Membership.find({ await Membership.find({
user: req.user._id user: req.user._id,
}).populate('workspace') }).populate("workspace")
) )
.filter((m) => workspacesSet.has(m.workspace._id.toString())) .filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace); .map((m) => m.workspace);
return res.status(200).send({ return res.status(200).send({
workspaces workspaces,
}); });
}; };
@@ -137,19 +136,19 @@ export const changeOrganizationName = async (req: Request, res: Response) => {
const organization = await Organization.findOneAndUpdate( const organization = await Organization.findOneAndUpdate(
{ {
_id: organizationId _id: organizationId,
}, },
{ {
name name,
}, },
{ {
new: true new: true,
} }
); );
return res.status(200).send({ return res.status(200).send({
message: 'Successfully changed organization name', message: "Successfully changed organization name",
organization organization,
}); });
}; };
@@ -166,11 +165,11 @@ export const getOrganizationIncidentContacts = async (
const { organizationId } = req.params; const { organizationId } = req.params;
const incidentContactsOrg = await IncidentContactOrg.find({ const incidentContactsOrg = await IncidentContactOrg.find({
organization: organizationId organization: organizationId,
}); });
return res.status(200).send({ return res.status(200).send({
incidentContactsOrg incidentContactsOrg,
}); });
}; };
@@ -194,7 +193,7 @@ export const addOrganizationIncidentContact = async (
); );
return res.status(200).send({ return res.status(200).send({
incidentContactOrg incidentContactOrg,
}); });
}; };
@@ -213,12 +212,12 @@ export const deleteOrganizationIncidentContact = async (
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({ const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
email, email,
organization: organizationId organization: organizationId,
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully deleted organization incident contact', message: "Successfully deleted organization incident contact",
incidentContactOrg incidentContactOrg,
}); });
}; };
@@ -235,28 +234,28 @@ export const createOrganizationPortalSession = async (
) => { ) => {
let session; let session;
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
// check if there is a payment method on file // check if there is a payment method on file
const paymentMethods = await stripe.paymentMethods.list({ const paymentMethods = await stripe.paymentMethods.list({
customer: req.organization.customerId, customer: req.organization.customerId,
type: 'card' type: "card",
}); });
if (paymentMethods.data.length < 1) { if (paymentMethods.data.length < 1) {
// case: no payment method on file // case: no payment method on file
session = await stripe.checkout.sessions.create({ session = await stripe.checkout.sessions.create({
customer: req.organization.customerId, customer: req.organization.customerId,
mode: 'setup', mode: "setup",
payment_method_types: ['card'], payment_method_types: ["card"],
success_url: (await getSiteURL()) + '/dashboard', success_url: (await getSiteURL()) + "/dashboard",
cancel_url: (await getSiteURL()) + '/dashboard' cancel_url: (await getSiteURL()) + "/dashboard",
}); });
} else { } else {
session = await stripe.billingPortal.sessions.create({ session = await stripe.billingPortal.sessions.create({
customer: req.organization.customerId, customer: req.organization.customerId,
return_url: (await getSiteURL()) + '/dashboard' return_url: (await getSiteURL()) + "/dashboard",
}); });
} }
@@ -274,15 +273,15 @@ export const getOrganizationSubscriptions = async (
res: Response res: Response
) => { ) => {
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
const subscriptions = await stripe.subscriptions.list({ const subscriptions = await stripe.subscriptions.list({
customer: req.organization.customerId customer: req.organization.customerId,
}); });
return res.status(200).send({ return res.status(200).send({
subscriptions subscriptions,
}); });
}; };
@@ -302,16 +301,16 @@ export const getOrganizationMembersAndTheirWorkspaces = async (
const workspacesSet = ( const workspacesSet = (
await Workspace.find( await Workspace.find(
{ {
organization: organizationId organization: organizationId,
}, },
'_id' "_id"
) )
).map((w) => w._id.toString()); ).map((w) => w._id.toString());
const memberships = ( const memberships = (
await Membership.find({ await Membership.find({
workspace: { $in: workspacesSet } workspace: { $in: workspacesSet },
}).populate('workspace') }).populate("workspace")
); );
const userToWorkspaceIds: any = {}; const userToWorkspaceIds: any = {};
@@ -1,25 +1,18 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require('jsrp'); const jsrp = require("jsrp");
import * as bigintConversion from 'bigint-conversion'; import * as bigintConversion from "bigint-conversion";
import { User, BackupPrivateKey, LoginSRPDetail } from '../../models'; import { BackupPrivateKey, LoginSRPDetail, User } from "../../models";
import { clearTokens, createToken, sendMail } from "../../helpers";
import { TokenService } from "../../services";
import { AUTH_MODE_JWT, TOKEN_EMAIL_PASSWORD_RESET } from "../../variables";
import { BadRequestError } from "../../utils/errors";
import { import {
createToken, getHttpsEnabled,
sendMail, getJwtSignupLifetime,
clearTokens getJwtSignupSecret,
} from '../../helpers'; getSiteURL
import { TokenService } from '../../services'; } from "../../config";
import {
TOKEN_EMAIL_PASSWORD_RESET,
AUTH_MODE_JWT
} from '../../variables';
import { BadRequestError } from '../../utils/errors';
import {
getSiteURL,
getJwtSignupLifetime,
getJwtSignupSecret,
getHttpsEnabled
} from '../../config';
/** /**
* Password reset step 1: Send email verification link to email [email] * Password reset step 1: Send email verification link to email [email]
@@ -29,15 +22,14 @@ import {
* @returns * @returns
*/ */
export const emailPasswordReset = async (req: Request, res: Response) => { export const emailPasswordReset = async (req: Request, res: Response) => {
let email: string; const email: string = req.body.email;
email = req.body.email;
const user = await User.findOne({ email }).select('+publicKey'); const user = await User.findOne({ email }).select("+publicKey");
if (!user || !user?.publicKey) { if (!user || !user?.publicKey) {
// case: user has already completed account // case: user has already completed account
return res.status(200).send({ return res.status(200).send({
message:"If an account exists with this email, a password reset link has been sent" message: "If an account exists with this email, a password reset link has been sent"
}); });
} }
@@ -47,20 +39,20 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
}); });
await sendMail({ await sendMail({
template: 'passwordReset.handlebars', template: "passwordReset.handlebars",
subjectLine: 'Infisical password reset', subjectLine: "Infisical password reset",
recipients: [email], recipients: [email],
substitutions: { substitutions: {
email, email,
token, token,
callback_url: (await getSiteURL()) + '/password-reset' callback_url: (await getSiteURL()) + "/password-reset"
} }
}); });
return res.status(200).send({ return res.status(200).send({
message:"If an account exists with this email, a password reset link has been sent" message: "If an account exists with this email, a password reset link has been sent"
}); });
} };
/** /**
* Password reset step 2: Verify email verification link sent to email [email] * Password reset step 2: Verify email verification link sent to email [email]
@@ -71,12 +63,12 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
export const emailPasswordResetVerify = async (req: Request, res: Response) => { export const emailPasswordResetVerify = async (req: Request, res: Response) => {
const { email, code } = req.body; const { email, code } = req.body;
const user = await User.findOne({ email }).select('+publicKey'); const user = await User.findOne({ email }).select("+publicKey");
if (!user || !user?.publicKey) { if (!user || !user?.publicKey) {
// case: user doesn't exist with email [email] or // case: user doesn't exist with email [email] or
// hasn't even completed their account // hasn't even completed their account
return res.status(403).send({ return res.status(403).send({
error: 'Failed email verification for password reset' error: "Failed email verification for password reset"
}); });
} }
@@ -95,12 +87,12 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
secret: await getJwtSignupSecret() secret: await getJwtSignupSecret()
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully verified email', message: "Successfully verified email",
user, user,
token token
}); });
} };
/** /**
* Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol * Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
@@ -109,14 +101,14 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const srp1 = async (req: Request, res: Response) => { export const srp1 = async (req: Request, res: Response) => {
// return salt, serverPublicKey as part of first step of SRP protocol // return salt, serverPublicKey as part of first step of SRP protocol
const { clientPublicKey } = req.body; const { clientPublicKey } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email: req.user.email email: req.user.email
}).select('+salt +verifier'); }).select("+salt +verifier");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const server = new jsrp.server(); const server = new jsrp.server();
server.init( server.init(
@@ -128,11 +120,15 @@ export const srp1 = async (req: Request, res: Response) => {
// generate server-side public key // generate server-side public key
const serverPublicKey = server.getPublicKey(); const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, { await LoginSRPDetail.findOneAndReplace(
email: req.user.email, { email: req.user.email },
clientPublicKey: clientPublicKey, {
serverBInt: bigintConversion.bigintToBuf(server.bInt), email: req.user.email,
}, { upsert: true, returnNewDocument: false }) clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
@@ -140,8 +136,7 @@ export const srp1 = async (req: Request, res: Response) => {
}); });
} }
); );
} };
/** /**
* Change account SRP authentication information for user * Change account SRP authentication information for user
@@ -166,14 +161,18 @@ export const changePassword = async (req: Request, res: Response) => {
const user = await User.findOne({ const user = await User.findOne({
email: req.user.email email: req.user.email
}).select('+salt +verifier'); }).select("+salt +verifier");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email }) const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email });
if (!loginSRPDetailFromDB) { if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
} }
const server = new jsrp.server(); const server = new jsrp.server();
@@ -208,26 +207,30 @@ export const changePassword = async (req: Request, res: Response) => {
} }
); );
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) { if (
await clearTokens(req.authData.tokenVersionId) req.authData.authMode === AUTH_MODE_JWT &&
req.authData.authPayload instanceof User &&
req.authData.tokenVersionId
) {
await clearTokens(req.authData.tokenVersionId);
} }
// clear httpOnly cookie // clear httpOnly cookie
res.cookie('jid', '', { res.cookie("jid", "", {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean secure: (await getHttpsEnabled()) as boolean
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully changed password' message: "Successfully changed password"
}); });
} }
return res.status(400).send({ return res.status(400).send({
error: 'Failed to change password. Try again?' error: "Failed to change password. Try again?"
}); });
} }
); );
@@ -240,22 +243,25 @@ export const changePassword = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const createBackupPrivateKey = async (req: Request, res: Response) => { export const createBackupPrivateKey = async (req: Request, res: Response) => {
// create/change backup private key // create/change backup private key
// requires verifying [clientProof] as part of second step of SRP protocol // requires verifying [clientProof] as part of second step of SRP protocol
// as initiated in /srp1 // as initiated in /srp1
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = req.body;
req.body;
const user = await User.findOne({ const user = await User.findOne({
email: req.user.email email: req.user.email
}).select('+salt +verifier'); }).select("+salt +verifier");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email }) const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email });
if (!loginSRPDetailFromDB) { if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again")) return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
} }
const server = new jsrp.server(); const server = new jsrp.server();
@@ -266,9 +272,7 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
b: loginSRPDetailFromDB.serverBInt b: loginSRPDetailFromDB.serverBInt
}, },
async () => { async () => {
server.setClientPublicKey( server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
loginSRPDetailFromDB.clientPublicKey
);
// compare server and client shared keys // compare server and client shared keys
if (server.checkClientProof(clientProof)) { if (server.checkClientProof(clientProof)) {
@@ -285,17 +289,17 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
verifier verifier
}, },
{ upsert: true, new: true } { upsert: true, new: true }
).select('+user, encryptedPrivateKey'); ).select("+user, encryptedPrivateKey");
// issue tokens // issue tokens
return res.status(200).send({ return res.status(200).send({
message: 'Successfully updated backup private key', message: "Successfully updated backup private key",
backupPrivateKey backupPrivateKey
}); });
} }
return res.status(400).send({ return res.status(400).send({
message: 'Failed to update backup private key' message: "Failed to update backup private key"
}); });
} }
); );
@@ -310,14 +314,14 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
export const getBackupPrivateKey = async (req: Request, res: Response) => { export const getBackupPrivateKey = async (req: Request, res: Response) => {
const backupPrivateKey = await BackupPrivateKey.findOne({ const backupPrivateKey = await BackupPrivateKey.findOne({
user: req.user._id user: req.user._id
}).select('+encryptedPrivateKey +iv +tag'); }).select("+encryptedPrivateKey +iv +tag");
if (!backupPrivateKey) throw new Error('Failed to find backup private key'); if (!backupPrivateKey) throw new Error("Failed to find backup private key");
return res.status(200).send({ return res.status(200).send({
backupPrivateKey backupPrivateKey
}); });
} };
export const resetPassword = async (req: Request, res: Response) => { export const resetPassword = async (req: Request, res: Response) => {
const { const {
@@ -328,7 +332,7 @@ export const resetPassword = async (req: Request, res: Response) => {
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier, verifier
} = req.body; } = req.body;
await User.findByIdAndUpdate( await User.findByIdAndUpdate(
@@ -349,7 +353,7 @@ export const resetPassword = async (req: Request, res: Response) => {
} }
); );
return res.status(200).send({ return res.status(200).send({
message: 'Successfully reset password' message: "Successfully reset password"
}); });
} };
+117 -124
View File
@@ -1,30 +1,30 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { Key, Secret } from '../../models'; import { Key } from "../../models";
import { import {
v1PushSecrets as push, pullSecrets as pull,
pullSecrets as pull, v1PushSecrets as push,
reformatPullSecrets reformatPullSecrets
} from '../../helpers/secret'; } from "../../helpers/secret";
import { pushKeys } from '../../helpers/key'; import { pushKeys } from "../../helpers/key";
import { eventPushSecrets } from '../../events'; import { eventPushSecrets } from "../../events";
import { EventService } from '../../services'; import { EventService } from "../../services";
import { TelemetryService } from '../../services'; import { TelemetryService } from "../../services";
interface PushSecret { interface PushSecret {
ciphertextKey: string; ciphertextKey: string;
ivKey: string; ivKey: string;
tagKey: string; tagKey: string;
hashKey: string; hashKey: string;
ciphertextValue: string; ciphertextValue: string;
ivValue: string; ivValue: string;
tagValue: string; tagValue: string;
hashValue: string; hashValue: string;
ciphertextComment: string; ciphertextComment: string;
ivComment: string; ivComment: string;
tagComment: string; tagComment: string;
hashComment: string; hashComment: string;
type: 'shared' | 'personal'; type: "shared" | "personal";
} }
/** /**
@@ -35,7 +35,7 @@ interface PushSecret {
* @returns * @returns
*/ */
export const pushSecrets = async (req: Request, res: Response) => { export const pushSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId] // upload (encrypted) secrets to workspace with id [workspaceId]
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: PushSecret[] } = req.body; let { secrets }: { secrets: PushSecret[] } = req.body;
const { keys, environment, channel } = req.body; const { keys, environment, channel } = req.body;
@@ -44,13 +44,11 @@ export const pushSecrets = async (req: Request, res: Response) => {
// validate environment // validate environment
const workspaceEnvs = req.membership.workspace.environments; const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment'); throw new Error("Failed to validate environment");
} }
// sanitize secrets // sanitize secrets
secrets = secrets.filter( secrets = secrets.filter((s: PushSecret) => s.ciphertextKey !== "" && s.ciphertextValue !== "");
(s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== ''
);
await push({ await push({
userId: req.user._id, userId: req.user._id,
@@ -65,16 +63,15 @@ export const pushSecrets = async (req: Request, res: Response) => {
keys keys
}); });
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets pushed', event: "secrets pushed",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
environment, environment,
workspaceId, workspaceId,
channel: channel ? channel : 'cli' channel: channel ? channel : "cli"
} }
}); });
} }
@@ -87,9 +84,9 @@ export const pushSecrets = async (req: Request, res: Response) => {
}) })
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully uploaded workspace secrets' message: "Successfully uploaded workspace secrets"
}); });
}; };
/** /**
@@ -100,57 +97,56 @@ export const pushSecrets = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const pullSecrets = async (req: Request, res: Response) => { export const pullSecrets = async (req: Request, res: Response) => {
let secrets; let secrets;
let key;
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string; const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string; const channel: string = req.query.channel as string;
const { workspaceId } = req.params; const { workspaceId } = req.params;
// validate environment // validate environment
const workspaceEnvs = req.membership.workspace.environments; const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment'); throw new Error("Failed to validate environment");
} }
secrets = await pull({ secrets = await pull({
userId: req.user._id.toString(), userId: req.user._id.toString(),
workspaceId, workspaceId,
environment, environment,
channel: channel ? channel : 'cli', channel: channel ? channel : "cli",
ipAddress: req.realIP ipAddress: req.realIP
}); });
key = await Key.findOne({ const key = await Key.findOne({
workspace: workspaceId, workspace: workspaceId,
receiver: req.user._id receiver: req.user._id
}) })
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.populate('sender', '+publicKey'); .populate("sender", "+publicKey");
if (channel !== 'cli') { if (channel !== "cli") {
secrets = reformatPullSecrets({ secrets }); secrets = reformatPullSecrets({ secrets });
} }
if (postHogClient) { if (postHogClient) {
// capture secrets pushed event in production // capture secrets pushed event in production
postHogClient.capture({ postHogClient.capture({
distinctId: req.user.email, distinctId: req.user.email,
event: 'secrets pulled', event: "secrets pulled",
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
environment, environment,
workspaceId, workspaceId,
channel: channel ? channel : 'cli' channel: channel ? channel : "cli"
} }
}); });
} }
return res.status(200).send({ return res.status(200).send({
secrets, secrets,
key key
}); });
}; };
/** /**
@@ -162,54 +158,51 @@ export const pullSecrets = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const pullSecretsServiceToken = async (req: Request, res: Response) => { export const pullSecretsServiceToken = async (req: Request, res: Response) => {
let secrets; const postHogClient = await TelemetryService.getPostHogClient();
let key; const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
const postHogClient = await TelemetryService.getPostHogClient(); // validate environment
const environment: string = req.query.environment as string; const workspaceEnvs = req.membership.workspace.environments;
const channel: string = req.query.channel as string; if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
const { workspaceId } = req.params; throw new Error("Failed to validate environment");
}
// validate environment const secrets = await pull({
const workspaceEnvs = req.membership.workspace.environments; userId: req.serviceToken.user._id.toString(),
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { workspaceId,
throw new Error('Failed to validate environment'); environment,
} channel: "cli",
ipAddress: req.realIP
});
secrets = await pull({ const key = {
userId: req.serviceToken.user._id.toString(), encryptedKey: req.serviceToken.encryptedKey,
workspaceId, nonce: req.serviceToken.nonce,
environment, sender: {
channel: 'cli', publicKey: req.serviceToken.publicKey
ipAddress: req.realIP },
}); receiver: req.serviceToken.user,
workspace: req.serviceToken.workspace
};
key = { if (postHogClient) {
encryptedKey: req.serviceToken.encryptedKey, // capture secrets pulled event in production
nonce: req.serviceToken.nonce, postHogClient.capture({
sender: { distinctId: req.serviceToken.user.email,
publicKey: req.serviceToken.publicKey event: "secrets pulled",
}, properties: {
receiver: req.serviceToken.user, numberOfSecrets: secrets.length,
workspace: req.serviceToken.workspace environment,
}; workspaceId,
channel: channel ? channel : "cli"
}
});
}
if (postHogClient) { return res.status(200).send({
// capture secrets pulled event in production secrets: reformatPullSecrets({ secrets }),
postHogClient.capture({ key
distinctId: req.serviceToken.user.email, });
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
return res.status(200).send({
secrets: reformatPullSecrets({ secrets }),
key
});
}; };
@@ -5,13 +5,13 @@ import { BadRequestError } from "../../utils/errors";
import { import {
appendFolder, appendFolder,
deleteFolderById, deleteFolderById,
getAllFolderIds,
searchByFolderIdWithDir,
searchByFolderId,
validateFolderName,
generateFolderId, generateFolderId,
getParentFromFolderId, getAllFolderIds,
getFolderByPath, getFolderByPath,
getParentFromFolderId,
searchByFolderId,
searchByFolderIdWithDir,
validateFolderName,
} from "../../services/FolderService"; } from "../../services/FolderService";
import { ADMIN, MEMBER } from "../../variables"; import { ADMIN, MEMBER } from "../../variables";
import { validateMembership } from "../../helpers/membership"; import { validateMembership } from "../../helpers/membership";
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { ServiceToken } from '../../models'; import { ServiceToken } from "../../models";
import { createToken } from '../../helpers/auth'; import { createToken } from "../../helpers/auth";
import { getJwtServiceSecret } from '../../config'; import { getJwtServiceSecret } from "../../config";
/** /**
* Return service token on request * Return service token on request
@@ -11,7 +11,7 @@ import { getJwtServiceSecret } from '../../config';
*/ */
export const getServiceToken = async (req: Request, res: Response) => { export const getServiceToken = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
serviceToken: req.serviceToken serviceToken: req.serviceToken,
}); });
}; };
@@ -31,13 +31,13 @@ export const createServiceToken = async (req: Request, res: Response) => {
expiresIn, expiresIn,
publicKey, publicKey,
encryptedKey, encryptedKey,
nonce nonce,
} = req.body; } = req.body;
// validate environment // validate environment
const workspaceEnvs = req.membership.workspace.environments; const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment'); throw new Error("Failed to validate environment");
} }
// compute access token expiration date // compute access token expiration date
@@ -52,24 +52,24 @@ export const createServiceToken = async (req: Request, res: Response) => {
expiresAt, expiresAt,
publicKey, publicKey,
encryptedKey, encryptedKey,
nonce nonce,
}).save(); }).save();
token = createToken({ token = createToken({
payload: { payload: {
serviceTokenId: serviceToken._id.toString(), serviceTokenId: serviceToken._id.toString(),
workspaceId workspaceId,
}, },
expiresIn: expiresIn, expiresIn: expiresIn,
secret: await getJwtServiceSecret() secret: await getJwtServiceSecret(),
}); });
} catch (err) { } catch (err) {
return res.status(400).send({ return res.status(400).send({
message: 'Failed to create service token' message: "Failed to create service token",
}); });
} }
return res.status(200).send({ return res.status(200).send({
token token,
}); });
}; };
+30 -27
View File
@@ -1,13 +1,15 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { User } from '../../models'; import { User } from "../../models";
import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup";
import { createToken } from "../../helpers/auth";
import { BadRequestError } from "../../utils/errors";
import { import {
sendEmailVerification, getInviteOnlySignup,
checkEmailVerification, getJwtSignupLifetime,
} from '../../helpers/signup'; getJwtSignupSecret,
import { createToken } from '../../helpers/auth'; getSmtpConfigured
import { BadRequestError } from '../../utils/errors'; } from "../../config";
import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config'; import { validateUserEmail } from "../../validation";
import { validateUserEmail } from '../../validation';
/** /**
* Signup step 1: Initialize account for user under email [email] and send a verification code * Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -17,27 +19,26 @@ import { validateUserEmail } from '../../validation';
* @returns * @returns
*/ */
export const beginEmailSignup = async (req: Request, res: Response) => { export const beginEmailSignup = async (req: Request, res: Response) => {
let email: string; const email: string = req.body.email;
email = req.body.email;
// validate that email is not disposable // validate that email is not disposable
validateUserEmail(email); validateUserEmail(email);
const user = await User.findOne({ email }).select('+publicKey'); const user = await User.findOne({ email }).select("+publicKey");
if (user && user?.publicKey) { if (user && user?.publicKey) {
// case: user has already completed account // case: user has already completed account
return res.status(403).send({ return res.status(403).send({
error: 'Failed to send email verification code for complete account' error: "Failed to send email verification code for complete account"
}); });
} }
// send send verification email // send send verification email
await sendEmailVerification({ email }); await sendEmailVerification({ email });
return res.status(200).send({ return res.status(200).send({
message: `Sent an email verification code to ${email}` message: `Sent an email verification code to ${email}`
}); });
}; };
/** /**
@@ -48,23 +49,25 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const verifyEmailSignup = async (req: Request, res: Response) => { export const verifyEmailSignup = async (req: Request, res: Response) => {
let user, token; let user;
const { email, code } = req.body; const { email, code } = req.body;
// initialize user account // initialize user account
user = await User.findOne({ email }).select('+publicKey'); user = await User.findOne({ email }).select("+publicKey");
if (user && user?.publicKey) { if (user && user?.publicKey) {
// case: user has already completed account // case: user has already completed account
return res.status(403).send({ return res.status(403).send({
error: 'Failed email verification for complete user' error: "Failed email verification for complete user"
}); });
} }
if (await getInviteOnlySignup()) { if (await getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account // 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({}) const userCount = await User.countDocuments({});
if (userCount != 0) { if (userCount != 0) {
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." }) throw BadRequestError({
message: "New user sign ups are not allowed at this time. You must be invited to sign up."
});
} }
} }
@@ -83,7 +86,7 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
} }
// generate temporary signup token // generate temporary signup token
token = createToken({ const token = createToken({
payload: { payload: {
userId: user._id.toString() userId: user._id.toString()
}, },
@@ -91,9 +94,9 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
secret: await getJwtSignupSecret() secret: await getJwtSignupSecret()
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfuly verified email', message: "Successfuly verified email",
user, user,
token token
}); });
}; };
@@ -1,6 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import Stripe from 'stripe'; import Stripe from "stripe";
import { getStripeSecretKey, getStripeWebhookSecret } from '../../config'; import { getStripeSecretKey, getStripeWebhookSecret } from "../../config";
/** /**
* Handle service provisioning/un-provisioning via Stripe * Handle service provisioning/un-provisioning via Stripe
@@ -11,10 +11,10 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../config';
export const handleWebhook = async (req: Request, res: Response) => { export const handleWebhook = async (req: Request, res: Response) => {
// check request for valid stripe signature // check request for valid stripe signature
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
const sig = req.headers['stripe-signature'] as string; const sig = req.headers["stripe-signature"] as string;
const event = stripe.webhooks.constructEvent( const event = stripe.webhooks.constructEvent(
req.body, req.body,
sig, sig,
@@ -22,7 +22,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
); );
switch (event.type) { switch (event.type) {
case '': case "":
break; break;
default: default:
} }
@@ -1,5 +1,5 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { UserAction } from '../../models'; import { UserAction } from "../../models";
/** /**
* Add user action [action] * Add user action [action]
@@ -15,18 +15,18 @@ export const addUserAction = async (req: Request, res: Response) => {
const userAction = await UserAction.findOneAndUpdate( const userAction = await UserAction.findOneAndUpdate(
{ {
user: req.user._id, user: req.user._id,
action action,
}, },
{ user: req.user._id, action }, { user: req.user._id, action },
{ {
new: true, new: true,
upsert: true upsert: true,
} }
); );
return res.status(200).send({ return res.status(200).send({
message: 'Successfully recorded user action', message: "Successfully recorded user action",
userAction userAction,
}); });
}; };
@@ -42,10 +42,10 @@ export const getUserAction = async (req: Request, res: Response) => {
const userAction = await UserAction.findOne({ const userAction = await UserAction.findOne({
user: req.user._id, user: req.user._id,
action action,
}); });
return res.status(200).send({ return res.status(200).send({
userAction userAction,
}); });
}; };
+2 -2
View File
@@ -1,4 +1,4 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
/** /**
* Return user on request * Return user on request
@@ -8,6 +8,6 @@ import { Request, Response } from 'express';
*/ */
export const getUser = async (req: Request, res: Response) => { export const getUser = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
user: req.user user: req.user,
}); });
}; };
@@ -1,19 +1,18 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { import {
Workspace, IUser,
Membership,
MembershipOrg,
Integration, Integration,
IntegrationAuth, IntegrationAuth,
IUser, Membership,
MembershipOrg,
ServiceToken, ServiceToken,
ServiceTokenData, Workspace,
} from "../../models"; } from "../../models";
import { import {
createWorkspace as create, createWorkspace as create,
deleteWorkspace as deleteWork, deleteWorkspace as deleteWork,
} from "../../helpers/workspace"; } from "../../helpers/workspace";
import { EELicenseService } from '../../ee/services'; import { EELicenseService } from "../../ee/services";
import { addMemberships } from "../../helpers/membership"; import { addMemberships } from "../../helpers/membership";
import { ADMIN } from "../../variables"; import { ADMIN } from "../../variables";
@@ -123,7 +122,7 @@ export const createWorkspace = async (req: Request, res: Response) => {
if (plan.workspacesUsed >= plan.workspaceLimit) { if (plan.workspacesUsed >= plan.workspaceLimit) {
// case: number of workspaces used exceeds the number of workspaces allowed // case: number of workspaces used exceeds the number of workspaces allowed
return res.status(400).send({ return res.status(400).send({
message: 'Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.' message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.",
}); });
} }
} }
@@ -1,10 +1,8 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import crypto from 'crypto'; import crypto from "crypto";
import bcrypt from 'bcrypt'; import bcrypt from "bcrypt";
import { import { APIKeyData } from "../../models";
APIKeyData import { getSaltRounds } from "../../config";
} from '../../models';
import { getSaltRounds } from '../../config';
/** /**
* Return API key data for user with id [req.user_id] * Return API key data for user with id [req.user_id]
@@ -13,14 +11,14 @@ import { getSaltRounds } from '../../config';
* @returns * @returns
*/ */
export const getAPIKeyData = async (req: Request, res: Response) => { export const getAPIKeyData = async (req: Request, res: Response) => {
const apiKeyData = await APIKeyData.find({ const apiKeyData = await APIKeyData.find({
user: req.user._id user: req.user._id,
}); });
return res.status(200).send({ return res.status(200).send({
apiKeyData apiKeyData,
}); });
} };
/** /**
* Create new API key data for user with id [req.user._id] * Create new API key data for user with id [req.user._id]
@@ -28,35 +26,35 @@ export const getAPIKeyData = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const createAPIKeyData = async (req: Request, res: Response) => { export const createAPIKeyData = async (req: Request, res: Response) => {
const { name, expiresIn } = req.body; const { name, expiresIn } = req.body;
const secret = crypto.randomBytes(16).toString('hex'); const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
const expiresAt = new Date(); const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
let apiKeyData = await new APIKeyData({ let apiKeyData = await new APIKeyData({
name, name,
lastUsed: new Date(), lastUsed: new Date(),
expiresAt, expiresAt,
user: req.user._id, user: req.user._id,
secretHash secretHash,
}).save(); }).save();
// return api key data without sensitive data // return api key data without sensitive data
// FIX: fix this any // FIX: fix this any
apiKeyData = await APIKeyData.findById(apiKeyData._id) as any apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any;
if (!apiKeyData) throw new Error('Failed to find API key data'); if (!apiKeyData) throw new Error("Failed to find API key data");
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`; const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
return res.status(200).send({ return res.status(200).send({
apiKey, apiKey,
apiKeyData apiKeyData,
}); });
} };
/** /**
* Delete API key data with id [apiKeyDataId]. * Delete API key data with id [apiKeyDataId].
@@ -65,10 +63,10 @@ export const createAPIKeyData = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const deleteAPIKeyData = async (req: Request, res: Response) => { export const deleteAPIKeyData = async (req: Request, res: Response) => {
const { apiKeyDataId } = req.params; const { apiKeyDataId } = req.params;
const apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId); const apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId);
return res.status(200).send({ return res.status(200).send({
apiKeyData apiKeyData,
}); });
} };
+67 -67
View File
@@ -1,27 +1,27 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express'; import { Request, Response } from "express";
import jwt from 'jsonwebtoken'; import jwt from "jsonwebtoken";
import * as bigintConversion from 'bigint-conversion'; import * as bigintConversion from "bigint-conversion";
const jsrp = require('jsrp'); const jsrp = require("jsrp");
import { User, LoginSRPDetail } from '../../models'; import { LoginSRPDetail, User } from "../../models";
import { issueAuthTokens, createToken } from '../../helpers/auth'; import { createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from '../../helpers/user'; import { checkUserDevice } from "../../helpers/user";
import { sendMail } from '../../helpers/nodemailer'; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from '../../services'; import { TokenService } from "../../services";
import { EELogService } from '../../ee/services'; import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from '../../utils/errors'; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { import {
ACTION_LOGIN,
TOKEN_EMAIL_MFA, TOKEN_EMAIL_MFA,
ACTION_LOGIN } from "../../variables";
} from '../../variables'; import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
import { import {
getHttpsEnabled,
getJwtMfaLifetime, getJwtMfaLifetime,
getJwtMfaSecret, getJwtMfaSecret,
getHttpsEnabled } from "../../config";
} from '../../config';
declare module 'jsonwebtoken' { declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string; userId: string;
} }
@@ -36,20 +36,20 @@ declare module 'jsonwebtoken' {
export const login1 = async (req: Request, res: Response) => { export const login1 = async (req: Request, res: Response) => {
const { const {
email, email,
clientPublicKey clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body; }: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email email,
}).select('+salt +verifier'); }).select("+salt +verifier");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const server = new jsrp.server(); const server = new jsrp.server();
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier verifier: user.verifier,
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
@@ -63,7 +63,7 @@ export const login1 = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt salt: user.salt,
}); });
} }
); );
@@ -78,14 +78,14 @@ export const login1 = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email email,
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices'); }).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email }) const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
@@ -98,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetail.serverBInt b: loginSRPDetail.serverBInt,
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey); server.setClientPublicKey(loginSRPDetail.clientPublicKey);
@@ -111,52 +111,52 @@ export const login2 = async (req: Request, res: Response) => {
// generate temporary MFA token // generate temporary MFA token
const token = createToken({ const token = createToken({
payload: { payload: {
userId: user._id.toString() userId: user._id.toString(),
}, },
expiresIn: await getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret() secret: await getJwtMfaSecret(),
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email,
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: "emailMfa.handlebars",
subjectLine: 'Infisical MFA code', subjectLine: "Infisical MFA code",
recipients: [email], recipients: [email],
substitutions: { substitutions: {
code code,
} },
}); });
return res.status(200).send({ return res.status(200).send({
mfaEnabled: true, mfaEnabled: true,
token token,
}); });
} }
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
// case: user does not have MFA enabled // case: user does not have MFA enabled
@@ -182,7 +182,7 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag tag: user.tag,
} }
if ( if (
@@ -197,21 +197,21 @@ export const login2 = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id userId: user._id,
}); });
loginAction && await EELogService.createLog({ loginAction && await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.ip ipAddress: req.ip,
}); });
return res.status(200).send(response); return res.status(200).send(response);
} }
return res.status(400).send({ return res.status(400).send({
message: 'Failed to authenticate. Try again?' message: "Failed to authenticate. Try again?",
}); });
} }
); );
@@ -227,21 +227,21 @@ export const sendMfaToken = async (req: Request, res: Response) => {
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email,
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: "emailMfa.handlebars",
subjectLine: 'Infisical MFA code', subjectLine: "Infisical MFA code",
recipients: [email], recipients: [email],
substitutions: { substitutions: {
code code,
} },
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully sent new MFA code' message: "Successfully sent new MFA code",
}); });
} }
@@ -257,36 +257,36 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email,
token: mfaToken token: mfaToken,
}); });
const user = await User.findOne({ const user = await User.findOne({
email email,
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices'); }).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
await LoginSRPDetail.deleteOne({ userId: user.id }) await LoginSRPDetail.deleteOne({ userId: user.id })
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
interface VerifyMfaTokenRes { interface VerifyMfaTokenRes {
@@ -319,7 +319,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
publicKey: user.publicKey as string, publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string, encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string, iv: user.iv as string,
tag: user.tag as string tag: user.tag as string,
} }
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) { if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
@@ -330,14 +330,14 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id userId: user._id,
}); });
loginAction && await EELogService.createLog({ loginAction && await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP ipAddress: req.realIP,
}); });
return res.status(200).send(resObj); return res.status(200).send(resObj);
@@ -1,17 +1,17 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { import {
Integration,
Membership,
Secret, Secret,
ServiceToken, ServiceToken,
Workspace,
Integration,
ServiceTokenData, ServiceTokenData,
Membership, Workspace,
} from '../../models'; } from "../../models";
import { SecretVersion } from '../../ee/models'; import { SecretVersion } from "../../ee/models";
import { EELicenseService } from '../../ee/services'; import { EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from '../../utils/errors'; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from 'lodash'; import _ from "lodash";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables'; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
/** /**
* Create new workspace environment named [environmentName] under workspace with id * Create new workspace environment named [environmentName] under workspace with id
@@ -38,7 +38,7 @@ export const createWorkspaceEnvironment = async (
// case: number of environments used exceeds the number of environments allowed // case: number of environments used exceeds the number of environments allowed
return res.status(400).send({ return res.status(400).send({
message: 'Failed to create environment due to environment limit reached. Upgrade plan to create more environments.' message: "Failed to create environment due to environment limit reached. Upgrade plan to create more environments.",
}); });
} }
} }
@@ -49,7 +49,7 @@ export const createWorkspaceEnvironment = async (
({ name, slug }) => slug === environmentSlug || environmentName === name ({ name, slug }) => slug === environmentSlug || environmentName === name
) )
) { ) {
throw new Error('Failed to create workspace environment'); throw new Error("Failed to create workspace environment");
} }
workspace?.environments.push({ workspace?.environments.push({
@@ -61,7 +61,7 @@ export const createWorkspaceEnvironment = async (
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId); await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
return res.status(200).send({ return res.status(200).send({
message: 'Successfully created new environment', message: "Successfully created new environment",
workspace: workspaceId, workspace: workspaceId,
environment: { environment: {
name: environmentName, name: environmentName,
@@ -85,13 +85,13 @@ export const renameWorkspaceEnvironment = async (
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body; const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
// user should pass both new slug and env name // user should pass both new slug and env name
if (!environmentSlug || !environmentName) { if (!environmentSlug || !environmentName) {
throw new Error('Invalid environment given.'); throw new Error("Invalid environment given.");
} }
// atomic update the env to avoid conflict // atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec(); const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) { if (!workspace) {
throw new Error('Failed to create workspace environment'); throw new Error("Failed to create workspace environment");
} }
const isEnvExist = workspace.environments.some( const isEnvExist = workspace.environments.some(
@@ -100,14 +100,14 @@ export const renameWorkspaceEnvironment = async (
(name === environmentName || slug === environmentSlug) (name === environmentName || slug === environmentSlug)
); );
if (isEnvExist) { if (isEnvExist) {
throw new Error('Invalid environment given'); throw new Error("Invalid environment given");
} }
const envIndex = workspace?.environments.findIndex( const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug ({ slug }) => slug === oldEnvironmentSlug
); );
if (envIndex === -1) { if (envIndex === -1) {
throw new Error('Invalid environment given'); throw new Error("Invalid environment given");
} }
workspace.environments[envIndex].name = environmentName; workspace.environments[envIndex].name = environmentName;
@@ -137,7 +137,7 @@ export const renameWorkspaceEnvironment = async (
await Membership.updateMany( await Membership.updateMany(
{ {
workspace: workspaceId, workspace: workspaceId,
"deniedPermissions.environmentSlug": oldEnvironmentSlug "deniedPermissions.environmentSlug": oldEnvironmentSlug,
}, },
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } }, { $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] } { arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
@@ -145,7 +145,7 @@ export const renameWorkspaceEnvironment = async (
return res.status(200).send({ return res.status(200).send({
message: 'Successfully update environment', message: "Successfully update environment",
workspace: workspaceId, workspace: workspaceId,
environment: { environment: {
name: environmentName, name: environmentName,
@@ -169,14 +169,14 @@ export const deleteWorkspaceEnvironment = async (
// atomic update the env to avoid conflict // atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec(); const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) { if (!workspace) {
throw new Error('Failed to create workspace environment'); throw new Error("Failed to create workspace environment");
} }
const envIndex = workspace?.environments.findIndex( const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === environmentSlug ({ slug }) => slug === environmentSlug
); );
if (envIndex === -1) { if (envIndex === -1) {
throw new Error('Invalid environment given'); throw new Error("Invalid environment given");
} }
workspace.environments.splice(envIndex, 1); workspace.environments.splice(envIndex, 1);
@@ -211,7 +211,7 @@ export const deleteWorkspaceEnvironment = async (
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId); await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
return res.status(200).send({ return res.status(200).send({
message: 'Successfully deleted environment', message: "Successfully deleted environment",
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug, environment: environmentSlug,
}); });
@@ -225,7 +225,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
const { workspaceId } = req.params; const { workspaceId } = req.params;
const workspacesUserIsMemberOf = await Membership.findOne({ const workspacesUserIsMemberOf = await Membership.findOne({
workspace: workspaceId, workspace: workspaceId,
user: req.user user: req.user,
}) })
if (!workspacesUserIsMemberOf) { if (!workspacesUserIsMemberOf) {
@@ -249,7 +249,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
name: environment.name, name: environment.name,
slug: environment.slug, slug: environment.slug,
isWriteDenied: isWriteBlocked, isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked isReadDenied: isReadBlocked,
}) })
} }
}) })
+13 -13
View File
@@ -1,15 +1,15 @@
import * as authController from './authController'; import * as authController from "./authController";
import * as signupController from './signupController'; import * as signupController from "./signupController";
import * as usersController from './usersController'; import * as usersController from "./usersController";
import * as organizationsController from './organizationsController'; import * as organizationsController from "./organizationsController";
import * as workspaceController from './workspaceController'; import * as workspaceController from "./workspaceController";
import * as serviceTokenDataController from './serviceTokenDataController'; import * as serviceTokenDataController from "./serviceTokenDataController";
import * as apiKeyDataController from './apiKeyDataController'; import * as apiKeyDataController from "./apiKeyDataController";
import * as secretController from './secretController'; import * as secretController from "./secretController";
import * as secretsController from './secretsController'; import * as secretsController from "./secretsController";
import * as serviceAccountsController from './serviceAccountsController'; import * as serviceAccountsController from "./serviceAccountsController";
import * as environmentController from './environmentController'; import * as environmentController from "./environmentController";
import * as tagController from './tagController'; import * as tagController from "./tagController";
export { export {
authController, authController,
@@ -23,5 +23,5 @@ export {
secretsController, secretsController,
serviceAccountsController, serviceAccountsController,
environmentController, environmentController,
tagController tagController,
} }
@@ -1,13 +1,13 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import {
MembershipOrg,
Membership, Membership,
MembershipOrg,
ServiceAccount,
Workspace, Workspace,
ServiceAccount } from "../../models";
} from '../../models'; import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { deleteMembershipOrg } from '../../helpers/membershipOrg'; import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
/** /**
* Return memberships for organization with id [organizationId] * Return memberships for organization with id [organizationId]
@@ -51,11 +51,11 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
const { organizationId } = req.params; const { organizationId } = req.params;
const memberships = await MembershipOrg.find({ const memberships = await MembershipOrg.find({
organization: organizationId organization: organizationId,
}).populate('user', '+publicKey'); }).populate("user", "+publicKey");
return res.status(200).send({ return res.status(200).send({
memberships memberships,
}); });
} }
@@ -124,14 +124,14 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
const membership = await MembershipOrg.findByIdAndUpdate( const membership = await MembershipOrg.findByIdAndUpdate(
membershipId, membershipId,
{ {
role role,
}, { }, {
new: true new: true,
} }
); );
return res.status(200).send({ return res.status(200).send({
membership membership,
}); });
} }
@@ -182,15 +182,15 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
// delete organization membership // delete organization membership
const membership = await deleteMembershipOrg({ const membership = await deleteMembershipOrg({
membershipOrgId: membershipId membershipOrgId: membershipId,
}); });
await updateSubscriptionOrgQuantity({ await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString() organizationId: membership.organization.toString(),
}); });
return res.status(200).send({ return res.status(200).send({
membership membership,
}); });
} }
@@ -240,23 +240,23 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
( (
await Workspace.find( await Workspace.find(
{ {
organization: organizationId organization: organizationId,
}, },
'_id' "_id"
) )
).map((w) => w._id.toString()) ).map((w) => w._id.toString())
); );
const workspaces = ( const workspaces = (
await Membership.find({ await Membership.find({
user: req.user._id user: req.user._id,
}).populate('workspace') }).populate("workspace")
) )
.filter((m) => workspacesSet.has(m.workspace._id.toString())) .filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace); .map((m) => m.workspace);
return res.status(200).send({ return res.status(200).send({
workspaces workspaces,
}); });
} }
@@ -269,10 +269,10 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
const { organizationId } = req.params; const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({ const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId) organization: new Types.ObjectId(organizationId),
}); });
return res.status(200).send({ return res.status(200).send({
serviceAccounts serviceAccounts,
}); });
} }
+163 -101
View File
@@ -2,14 +2,29 @@ import to from "await-to-js";
import { Request, Response } from "express"; import { Request, Response } from "express";
import mongoose, { Types } from "mongoose"; import mongoose, { Types } from "mongoose";
import Secret, { ISecret } from "../../models/secret"; import Secret, { ISecret } from "../../models/secret";
import { CreateSecretRequestBody, ModifySecretRequestBody, SanitizedSecretForCreate, SanitizedSecretModify } from "../../types/secret"; import {
CreateSecretRequestBody,
ModifySecretRequestBody,
SanitizedSecretForCreate,
SanitizedSecretModify
} from "../../types/secret";
const { ValidationError } = mongoose.Error; const { ValidationError } = mongoose.Error;
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors'; import {
import { AnyBulkWriteOperation } from 'mongodb'; BadRequestError,
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, SECRET_PERSONAL, SECRET_SHARED } from "../../variables"; InternalServerError,
import { TelemetryService } from '../../services'; ValidationError as RouteValidationError,
UnauthorizedRequestError
} from "../../utils/errors";
import { AnyBulkWriteOperation } from "mongodb";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED
} from "../../variables";
import { TelemetryService } from "../../services";
import { User } from "../../models"; import { User } from "../../models";
import { AccountNotFoundError } from '../../utils/errors'; import { AccountNotFoundError } from "../../utils/errors";
/** /**
* Create secret for workspace with id [workspaceId] and environment [environment] * Create secret for workspace with id [workspaceId] and environment [environment]
@@ -19,7 +34,7 @@ import { AccountNotFoundError } from '../../utils/errors';
export const createSecret = async (req: Request, res: Response) => { export const createSecret = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const secretToCreate: CreateSecretRequestBody = req.body.secret; const secretToCreate: CreateSecretRequestBody = req.body.secret;
const { workspaceId, environment } = req.params const { workspaceId, environment } = req.params;
const sanitizedSecret: SanitizedSecretForCreate = { const sanitizedSecret: SanitizedSecretForCreate = {
secretKeyCiphertext: secretToCreate.secretKeyCiphertext, secretKeyCiphertext: secretToCreate.secretKeyCiphertext,
secretKeyIV: secretToCreate.secretKeyIV, secretKeyIV: secretToCreate.secretKeyIV,
@@ -39,32 +54,31 @@ export const createSecret = async (req: Request, res: Response) => {
user: new Types.ObjectId(req.user._id), user: new Types.ObjectId(req.user._id),
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8 keyEncoding: ENCODING_SCHEME_UTF8
} };
const [error, secret] = await to(Secret.create(sanitizedSecret).then());
const [error, secret] = await to(Secret.create(sanitizedSecret).then())
if (error instanceof ValidationError) { if (error instanceof ValidationError) {
throw RouteValidationError({ message: error.message, stack: error.stack }) throw RouteValidationError({ message: error.message, stack: error.stack });
} }
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets added', event: "secrets added",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
workspaceId, workspaceId,
environment, environment,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
res.status(200).send({ res.status(200).send({
secret secret
}) });
} };
/** /**
* Create many secrets for workspace wiht id [workspaceId] and environment [environment] * Create many secrets for workspace wiht id [workspaceId] and environment [environment]
@@ -74,10 +88,10 @@ export const createSecret = async (req: Request, res: Response) => {
export const createSecrets = async (req: Request, res: Response) => { export const createSecrets = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets; const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
const { workspaceId, environment } = req.params const { workspaceId, environment } = req.params;
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [] const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [];
secretsToCreate.forEach(rawSecret => { secretsToCreate.forEach((rawSecret) => {
const safeUpdateFields: SanitizedSecretForCreate = { const safeUpdateFields: SanitizedSecretForCreate = {
secretKeyCiphertext: rawSecret.secretKeyCiphertext, secretKeyCiphertext: rawSecret.secretKeyCiphertext,
secretKeyIV: rawSecret.secretKeyIV, secretKeyIV: rawSecret.secretKeyIV,
@@ -97,38 +111,44 @@ export const createSecrets = async (req: Request, res: Response) => {
user: new Types.ObjectId(req.user._id), user: new Types.ObjectId(req.user._id),
algorithm: ALGORITHM_AES_256_GCM, algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8 keyEncoding: ENCODING_SCHEME_UTF8
} };
sanitizedSecretesToCreate.push(safeUpdateFields) sanitizedSecretesToCreate.push(safeUpdateFields);
}) });
const [bulkCreateError, secrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then()) const [bulkCreateError, secrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then());
if (bulkCreateError) { if (bulkCreateError) {
if (bulkCreateError instanceof ValidationError) { if (bulkCreateError instanceof ValidationError) {
throw RouteValidationError({ message: bulkCreateError.message, stack: bulkCreateError.stack }) throw RouteValidationError({
message: bulkCreateError.message,
stack: bulkCreateError.stack
});
} }
throw InternalServerError({ message: "Unable to process your batch create request. Please try again", stack: bulkCreateError.stack }) throw InternalServerError({
message: "Unable to process your batch create request. Please try again",
stack: bulkCreateError.stack
});
} }
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets added', event: "secrets added",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: (secretsToCreate ?? []).length, numberOfSecrets: (secretsToCreate ?? []).length,
workspaceId, workspaceId,
environment, environment,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
res.status(200).send({ res.status(200).send({
secrets secrets
}) });
} };
/** /**
* Delete secrets in workspace with id [workspaceId] and environment [environment] * Delete secrets in workspace with id [workspaceId] and environment [environment]
@@ -137,52 +157,65 @@ export const createSecrets = async (req: Request, res: Response) => {
*/ */
export const deleteSecrets = async (req: Request, res: Response) => { export const deleteSecrets = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params const { workspaceId, environmentName } = req.params;
const secretIdsToDelete: string[] = req.body.secretIds const secretIdsToDelete: string[] = req.body.secretIds;
const [secretIdsUserCanDeleteError, secretIdsUserCanDelete] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) const [secretIdsUserCanDeleteError, secretIdsUserCanDelete] = await to(
Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()
);
if (secretIdsUserCanDeleteError) { if (secretIdsUserCanDeleteError) {
throw InternalServerError({ message: `Unable to fetch secrets you own: [error=${secretIdsUserCanDeleteError.message}]` }) throw InternalServerError({
message: `Unable to fetch secrets you own: [error=${secretIdsUserCanDeleteError.message}]`
});
} }
const secretsUserCanDeleteSet: Set<string> = new Set(secretIdsUserCanDelete.map(objectId => objectId._id.toString())); const secretsUserCanDeleteSet: Set<string> = new Set(
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = [] secretIdsUserCanDelete.map((objectId) => objectId._id.toString())
);
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = [];
let numSecretsDeleted = 0; let numSecretsDeleted = 0;
secretIdsToDelete.forEach(secretIdToDelete => { secretIdsToDelete.forEach((secretIdToDelete) => {
if (secretsUserCanDeleteSet.has(secretIdToDelete)) { if (secretsUserCanDeleteSet.has(secretIdToDelete)) {
const deleteOperation = { deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } } } const deleteOperation = {
deleteOperationsToPerform.push(deleteOperation) deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
};
deleteOperationsToPerform.push(deleteOperation);
numSecretsDeleted++; numSecretsDeleted++;
} else { } else {
throw RouteValidationError({ message: "You cannot delete secrets that you do not have access to" }) throw RouteValidationError({
message: "You cannot delete secrets that you do not have access to"
});
} }
}) });
const [bulkDeleteError, bulkDelete] = await to(Secret.bulkWrite(deleteOperationsToPerform).then()) const [bulkDeleteError] = await to(Secret.bulkWrite(deleteOperationsToPerform).then());
if (bulkDeleteError) { if (bulkDeleteError) {
if (bulkDeleteError instanceof ValidationError) { if (bulkDeleteError instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkDeleteError.stack }) throw RouteValidationError({
message: "Unable to apply modifications, please try again",
stack: bulkDeleteError.stack
});
} }
throw InternalServerError() throw InternalServerError();
} }
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets deleted', event: "secrets deleted",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: numSecretsDeleted, numberOfSecrets: numSecretsDeleted,
environment: environmentName, environment: environmentName,
workspaceId, workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
res.status(200).send() res.status(200).send();
} };
/** /**
* Delete secret with id [secretId] * Delete secret with id [secretId]
@@ -191,26 +224,26 @@ export const deleteSecrets = async (req: Request, res: Response) => {
*/ */
export const deleteSecret = async (req: Request, res: Response) => { export const deleteSecret = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
await Secret.findByIdAndDelete(req._secret._id) await Secret.findByIdAndDelete(req._secret._id);
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets deleted', event: "secrets deleted",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
workspaceId: req._secret.workspace.toString(), workspaceId: req._secret.workspace.toString(),
environment: req._secret.environment, environment: req._secret.environment,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
res.status(200).send({ res.status(200).send({
secret: req._secret secret: req._secret
}) });
} };
/** /**
* Update secrets for workspace with id [workspaceId] and environment [environment] * Update secrets for workspace with id [workspaceId] and environment [environment]
@@ -220,17 +253,21 @@ export const deleteSecret = async (req: Request, res: Response) => {
*/ */
export const updateSecrets = async (req: Request, res: Response) => { export const updateSecrets = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params const { workspaceId, environmentName } = req.params;
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets; const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(
Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()
);
if (secretIdsUserCanModifyError) { if (secretIdsUserCanModifyError) {
throw InternalServerError({ message: "Unable to fetch secrets you own" }) throw InternalServerError({ message: "Unable to fetch secrets you own" });
} }
const secretsUserCanModifySet: Set<string> = new Set(secretIdsUserCanModify.map(objectId => objectId._id.toString())); const secretsUserCanModifySet: Set<string> = new Set(
const updateOperationsToPerform: any = [] secretIdsUserCanModify.map((objectId) => objectId._id.toString())
);
const updateOperationsToPerform: any = [];
secretsModificationsRequested.forEach(userModifiedSecret => { secretsModificationsRequested.forEach((userModifiedSecret) => {
if (secretsUserCanModifySet.has(userModifiedSecret._id.toString())) { if (secretsUserCanModifySet.has(userModifiedSecret._id.toString())) {
const sanitizedSecret: SanitizedSecretModify = { const sanitizedSecret: SanitizedSecretModify = {
secretKeyCiphertext: userModifiedSecret.secretKeyCiphertext, secretKeyCiphertext: userModifiedSecret.secretKeyCiphertext,
@@ -244,41 +281,53 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentCiphertext: userModifiedSecret.secretCommentCiphertext, secretCommentCiphertext: userModifiedSecret.secretCommentCiphertext,
secretCommentIV: userModifiedSecret.secretCommentIV, secretCommentIV: userModifiedSecret.secretCommentIV,
secretCommentTag: userModifiedSecret.secretCommentTag, secretCommentTag: userModifiedSecret.secretCommentTag,
secretCommentHash: userModifiedSecret.secretCommentHash, secretCommentHash: userModifiedSecret.secretCommentHash
} };
const updateOperation = { updateOne: { filter: { _id: userModifiedSecret._id, workspace: workspaceId }, update: { $inc: { version: 1 }, $set: sanitizedSecret } } } const updateOperation = {
updateOperationsToPerform.push(updateOperation) updateOne: {
filter: { _id: userModifiedSecret._id, workspace: workspaceId },
update: { $inc: { version: 1 }, $set: sanitizedSecret }
}
};
updateOperationsToPerform.push(updateOperation);
} else { } else {
throw UnauthorizedRequestError({ message: "You do not have permission to modify one or more of the requested secrets" }) throw UnauthorizedRequestError({
message: "You do not have permission to modify one or more of the requested secrets"
});
} }
}) });
const [bulkModificationInfoError, bulkModificationInfo] = await to(Secret.bulkWrite(updateOperationsToPerform).then()) const [bulkModificationInfoError, bulkModificationInfo] = await to(
Secret.bulkWrite(updateOperationsToPerform).then()
);
if (bulkModificationInfoError) { if (bulkModificationInfoError) {
if (bulkModificationInfoError instanceof ValidationError) { if (bulkModificationInfoError instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkModificationInfoError.stack }) throw RouteValidationError({
message: "Unable to apply modifications, please try again",
stack: bulkModificationInfoError.stack
});
} }
throw InternalServerError() throw InternalServerError();
} }
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets modified', event: "secrets modified",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: (secretsModificationsRequested ?? []).length, numberOfSecrets: (secretsModificationsRequested ?? []).length,
environment: environmentName, environment: environmentName,
workspaceId, workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
return res.status(200).send() return res.status(200).send();
} };
/** /**
* Update a secret within workspace with id [workspaceId] and environment [environment] * Update a secret within workspace with id [workspaceId] and environment [environment]
@@ -288,12 +337,14 @@ export const updateSecrets = async (req: Request, res: Response) => {
*/ */
export const updateSecret = async (req: Request, res: Response) => { export const updateSecret = async (req: Request, res: Response) => {
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params const { workspaceId, environmentName } = req.params;
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret; const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
const [secretIdUserCanModifyError, secretIdUserCanModify] = await to(Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()) const [secretIdUserCanModifyError, secretIdUserCanModify] = await to(
Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then()
);
if (secretIdUserCanModifyError && !secretIdUserCanModify) { if (secretIdUserCanModifyError && !secretIdUserCanModify) {
throw BadRequestError() throw BadRequestError();
} }
const sanitizedSecret: SanitizedSecretModify = { const sanitizedSecret: SanitizedSecretModify = {
@@ -308,30 +359,38 @@ export const updateSecret = async (req: Request, res: Response) => {
secretCommentCiphertext: secretModificationsRequested.secretCommentCiphertext, secretCommentCiphertext: secretModificationsRequested.secretCommentCiphertext,
secretCommentIV: secretModificationsRequested.secretCommentIV, secretCommentIV: secretModificationsRequested.secretCommentIV,
secretCommentTag: secretModificationsRequested.secretCommentTag, secretCommentTag: secretModificationsRequested.secretCommentTag,
secretCommentHash: secretModificationsRequested.secretCommentHash, secretCommentHash: secretModificationsRequested.secretCommentHash
} };
const [error, singleModificationUpdate] = await to(Secret.updateOne({ _id: secretModificationsRequested._id, workspace: workspaceId }, { $inc: { version: 1 }, $set: sanitizedSecret }).then()) const [error, singleModificationUpdate] = await to(
Secret.updateOne(
{ _id: secretModificationsRequested._id, workspace: workspaceId },
{ $inc: { version: 1 }, $set: sanitizedSecret }
).then()
);
if (error instanceof ValidationError) { if (error instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: error.stack }) throw RouteValidationError({
message: "Unable to apply modifications, please try again",
stack: error.stack
});
} }
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets modified', event: "secrets modified",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: 1, numberOfSecrets: 1,
environment: environmentName, environment: environmentName,
workspaceId, workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
return res.status(200).send(singleModificationUpdate) return res.status(200).send(singleModificationUpdate);
} };
/** /**
* Return secrets for workspace with id [workspaceId], environment [environment] and user * Return secrets for workspace with id [workspaceId], environment [environment] and user
@@ -345,8 +404,8 @@ export const getSecrets = async (req: Request, res: Response) => {
const { environment } = req.query; const { environment } = req.query;
const { workspaceId } = req.params; const { workspaceId } = req.params;
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user let userId: Types.ObjectId | undefined = undefined; // used for getting personal secrets for user
let userEmail: string | undefined = undefined // used for posthog let userEmail: string | undefined = undefined; // used for posthog
if (req.user) { if (req.user) {
userId = req.user._id; userId = req.user._id;
userEmail = req.user.email; userEmail = req.user.email;
@@ -355,40 +414,43 @@ export const getSecrets = async (req: Request, res: Response) => {
if (req.serviceTokenData) { if (req.serviceTokenData) {
userId = req.serviceTokenData.user; userId = req.serviceTokenData.user;
const user = await User.findById(req.serviceTokenData.user, 'email'); const user = await User.findById(req.serviceTokenData.user, "email");
if (!user) throw AccountNotFoundError(); if (!user) throw AccountNotFoundError();
userEmail = user.email; userEmail = user.email;
} }
const [err, secrets] = await to(Secret.find( const [err, secrets] = await to(
{ Secret.find({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
$or: [{ user: userId }, { user: { $exists: false } }], $or: [{ user: userId }, { user: { $exists: false } }],
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] } type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
} }).then()
).then()) );
if (err) { if (err) {
throw RouteValidationError({ message: "Failed to get secrets, please try again", stack: err.stack }) throw RouteValidationError({
message: "Failed to get secrets, please try again",
stack: err.stack
});
} }
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets pulled', event: "secrets pulled",
distinctId: userEmail, distinctId: userEmail,
properties: { properties: {
numberOfSecrets: (secrets ?? []).length, numberOfSecrets: (secrets ?? []).length,
environment, environment,
workspaceId, workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli', channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.["user-agent"]
} }
}); });
} }
return res.json(secrets) return res.json(secrets);
} };
/** /**
* Return secret with id [secretId] * Return secret with id [secretId]
@@ -414,4 +476,4 @@ export const getSecret = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
secret: req._secret secret: req._secret
}); });
} };
@@ -3,19 +3,19 @@ import { Request, Response } from "express";
import { ISecret, Secret, ServiceTokenData } from "../../models"; import { ISecret, Secret, ServiceTokenData } from "../../models";
import { IAction, SecretVersion } from "../../ee/models"; import { IAction, SecretVersion } from "../../ee/models";
import { import {
SECRET_PERSONAL,
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
} from "../../variables"; } from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EventService } from "../../services"; import { EventService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { EESecretService, EELogService } from "../../ee/services"; import { EELogService, EESecretService } from "../../ee/services";
import { TelemetryService, SecretService } from "../../services"; import { SecretService, TelemetryService } from "../../services";
import { getChannelFromUserAgent } from "../../utils/posthog"; import { getChannelFromUserAgent } from "../../utils/posthog";
import { PERMISSION_WRITE_SECRETS } from "../../variables"; import { PERMISSION_WRITE_SECRETS } from "../../variables";
import { import {
@@ -25,7 +25,7 @@ import {
} from "../../ee/helpers/checkMembershipPermissions"; } from "../../ee/helpers/checkMembershipPermissions";
import Tag from "../../models/tag"; import Tag from "../../models/tag";
import _ from "lodash"; import _ from "lodash";
import { BatchSecretRequest, BatchSecret } from "../../types/secret"; import { BatchSecret, BatchSecretRequest } from "../../types/secret";
import Folder from "../../models/folder"; import Folder from "../../models/folder";
import { import {
getFolderByPath, getFolderByPath,
@@ -1,18 +1,18 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import crypto from 'crypto'; import crypto from "crypto";
import bcrypt from 'bcrypt'; import bcrypt from "bcrypt";
import { import {
ServiceAccount, ServiceAccount,
ServiceAccountKey, ServiceAccountKey,
ServiceAccountOrganizationPermission, ServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermission ServiceAccountWorkspacePermission,
} from '../../models'; } from "../../models";
import { import {
CreateServiceAccountDto CreateServiceAccountDto,
} from '../../interfaces/serviceAccounts/dto'; } from "../../interfaces/serviceAccounts/dto";
import { BadRequestError, ServiceAccountNotFoundError } from '../../utils/errors'; import { BadRequestError, ServiceAccountNotFoundError } from "../../utils/errors";
import { getSaltRounds } from '../../config'; import { getSaltRounds } from "../../config";
/** /**
* Return service account tied to the request (service account) client * Return service account tied to the request (service account) client
@@ -23,11 +23,11 @@ export const getCurrentServiceAccount = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id); const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id);
if (!serviceAccount) { if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' }); throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
} }
return res.status(200).send({ return res.status(200).send({
serviceAccount serviceAccount,
}); });
} }
@@ -42,11 +42,11 @@ export const getServiceAccountById = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findById(serviceAccountId); const serviceAccount = await ServiceAccount.findById(serviceAccountId);
if (!serviceAccount) { if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' }); throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
} }
return res.status(200).send({ return res.status(200).send({
serviceAccount serviceAccount,
}); });
} }
@@ -71,7 +71,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn); expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
} }
const secret = crypto.randomBytes(16).toString('base64'); const secret = crypto.randomBytes(16).toString("base64");
const secretHash = await bcrypt.hash(secret, await getSaltRounds()); const secretHash = await bcrypt.hash(secret, await getSaltRounds());
// create service account // create service account
@@ -82,7 +82,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
publicKey, publicKey,
lastUsed: new Date(), lastUsed: new Date(),
expiresAt, expiresAt,
secretHash secretHash,
}).save(); }).save();
const serviceAccountObj = serviceAccount.toObject(); const serviceAccountObj = serviceAccount.toObject();
@@ -91,14 +91,14 @@ export const createServiceAccount = async (req: Request, res: Response) => {
// provision default org-level permission for service account // provision default org-level permission for service account
await new ServiceAccountOrganizationPermission({ await new ServiceAccountOrganizationPermission({
serviceAccount: serviceAccount._id serviceAccount: serviceAccount._id,
}).save(); }).save();
const secretId = Buffer.from(serviceAccount._id.toString(), 'hex').toString('base64'); const secretId = Buffer.from(serviceAccount._id.toString(), "hex").toString("base64");
return res.status(200).send({ return res.status(200).send({
serviceAccountAccessKey: `sa.${secretId}.${secret}`, serviceAccountAccessKey: `sa.${secretId}.${secret}`,
serviceAccount: serviceAccountObj serviceAccount: serviceAccountObj,
}); });
} }
@@ -114,18 +114,18 @@ export const changeServiceAccountName = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findOneAndUpdate( const serviceAccount = await ServiceAccount.findOneAndUpdate(
{ {
_id: new Types.ObjectId(serviceAccountId) _id: new Types.ObjectId(serviceAccountId),
}, },
{ {
name name,
}, },
{ {
new: true new: true,
} }
); );
return res.status(200).send({ return res.status(200).send({
serviceAccount serviceAccount,
}); });
} }
@@ -140,7 +140,7 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
const { const {
workspaceId, workspaceId,
encryptedKey, encryptedKey,
nonce nonce,
} = req.body; } = req.body;
const serviceAccountKey = await new ServiceAccountKey({ const serviceAccountKey = await new ServiceAccountKey({
@@ -148,7 +148,7 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
nonce, nonce,
sender: req.user._id, sender: req.user._id,
serviceAccount: req.serviceAccount._d, serviceAccount: req.serviceAccount._d,
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId),
}).save(); }).save();
return serviceAccountKey; return serviceAccountKey;
@@ -161,11 +161,11 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
*/ */
export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => { export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => {
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({ const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
serviceAccount: req.serviceAccount._id serviceAccount: req.serviceAccount._id,
}).populate('workspace'); }).populate("workspace");
return res.status(200).send({ return res.status(200).send({
serviceAccountWorkspacePermissions serviceAccountWorkspacePermissions,
}); });
} }
@@ -182,34 +182,34 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
read = false, read = false,
write = false, write = false,
encryptedKey, encryptedKey,
nonce nonce,
} = req.body; } = req.body;
if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) { if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) {
return res.status(400).send({ return res.status(400).send({
message: 'Failed to validate workspace environment' message: "Failed to validate workspace environment",
}); });
} }
const existingPermission = await ServiceAccountWorkspacePermission.findOne({ const existingPermission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId), serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment environment,
}); });
if (existingPermission) throw BadRequestError({ message: 'Failed to add workspace permission to service account due to already-existing ' }); if (existingPermission) throw BadRequestError({ message: "Failed to add workspace permission to service account due to already-existing " });
const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({ const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({
serviceAccount: new Types.ObjectId(serviceAccountId), serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
read, read,
write write,
}).save(); }).save();
const existingServiceAccountKey = await ServiceAccountKey.findOne({ const existingServiceAccountKey = await ServiceAccountKey.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId), serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId),
}); });
if (!existingServiceAccountKey) { if (!existingServiceAccountKey) {
@@ -218,12 +218,12 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
nonce, nonce,
sender: req.user._id, sender: req.user._id,
serviceAccount: new Types.ObjectId(serviceAccountId), serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId),
}).save(); }).save();
} }
return res.status(200).send({ return res.status(200).send({
serviceAccountWorkspacePermission serviceAccountWorkspacePermission,
}); });
} }
@@ -240,19 +240,19 @@ export const deleteServiceAccountWorkspacePermission = async (req: Request, res:
const { serviceAccount, workspace } = serviceAccountWorkspacePermission; const { serviceAccount, workspace } = serviceAccountWorkspacePermission;
const count = await ServiceAccountWorkspacePermission.countDocuments({ const count = await ServiceAccountWorkspacePermission.countDocuments({
serviceAccount, serviceAccount,
workspace workspace,
}); });
if (count === 0) { if (count === 0) {
await ServiceAccountKey.findOneAndDelete({ await ServiceAccountKey.findOneAndDelete({
serviceAccount, serviceAccount,
workspace workspace,
}); });
} }
} }
return res.status(200).send({ return res.status(200).send({
serviceAccountWorkspacePermission serviceAccountWorkspacePermission,
}); });
} }
@@ -269,20 +269,20 @@ export const deleteServiceAccount = async (req: Request, res: Response) => {
if (serviceAccount) { if (serviceAccount) {
await ServiceAccountKey.deleteMany({ await ServiceAccountKey.deleteMany({
serviceAccount: serviceAccount._id serviceAccount: serviceAccount._id,
}); });
await ServiceAccountOrganizationPermission.deleteMany({ await ServiceAccountOrganizationPermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId) serviceAccount: new Types.ObjectId(serviceAccountId),
}); });
await ServiceAccountWorkspacePermission.deleteMany({ await ServiceAccountWorkspacePermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId) serviceAccount: new Types.ObjectId(serviceAccountId),
}); });
} }
return res.status(200).send({ return res.status(200).send({
serviceAccount serviceAccount,
}); });
} }
@@ -297,10 +297,10 @@ export const getServiceAccountKeys = async (req: Request, res: Response) => {
const serviceAccountKeys = await ServiceAccountKey.find({ const serviceAccountKeys = await ServiceAccountKey.find({
serviceAccount: req.serviceAccount._id, serviceAccount: req.serviceAccount._id,
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {}) ...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {}),
}); });
return res.status(200).send({ return res.status(200).send({
serviceAccountKeys serviceAccountKeys,
}); });
} }
@@ -1,13 +1,10 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import crypto from "crypto"; import crypto from "crypto";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { User, ServiceAccount, ServiceTokenData } from "../../models"; import { ServiceAccount, ServiceTokenData, User } from "../../models";
import { userHasWorkspaceAccess } from "../../ee/helpers/checkMembershipPermissions";
import { import {
PERMISSION_READ_SECRETS,
AUTH_MODE_JWT, AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
} from "../../variables"; } from "../../variables";
import { getSaltRounds } from "../../config"; import { getSaltRounds } from "../../config";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
+45 -45
View File
@@ -1,14 +1,14 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { User, MembershipOrg } from '../../models'; import { MembershipOrg, User } from "../../models";
import { completeAccount } from '../../helpers/user'; import { completeAccount } from "../../helpers/user";
import { import {
initializeDefaultOrg initializeDefaultOrg,
} from '../../helpers/signup'; } from "../../helpers/signup";
import { issueAuthTokens } from '../../helpers/auth'; import { issueAuthTokens } from "../../helpers/auth";
import { INVITED, ACCEPTED } from '../../variables'; import { ACCEPTED, INVITED } from "../../variables";
import { standardRequest } from '../../config/request'; import { standardRequest } from "../../config/request";
import { getLoopsApiKey, getHttpsEnabled } from '../../config'; import { getHttpsEnabled, getLoopsApiKey } from "../../config";
import { updateSubscriptionOrgQuantity } from '../../helpers/organization'; import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
/** /**
* Complete setting up user by adding their personal and auth information as part of the * Complete setting up user by adding their personal and auth information as part of the
@@ -32,7 +32,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier, verifier,
organizationName organizationName,
}: { }: {
email: string; email: string;
firstName: string; firstName: string;
@@ -56,7 +56,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// case 1: user doesn't exist. // case 1: user doesn't exist.
// case 2: user has already completed account // case 2: user has already completed account
return res.status(403).send({ return res.status(403).send({
error: 'Failed to complete account for complete user' error: "Failed to complete account for complete user",
}); });
} }
@@ -74,28 +74,28 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
}); });
if (!user) if (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
// initialize default organization and workspace // initialize default organization and workspace
await initializeDefaultOrg({ await initializeDefaultOrg({
organizationName, organizationName,
user user,
}); });
// update organization membership statuses that are // update organization membership statuses that are
// invited to completed with user attached // invited to completed with user attached
const membershipsToUpdate = await MembershipOrg.find({ const membershipsToUpdate = await MembershipOrg.find({
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
}); });
membershipsToUpdate.forEach(async (membership) => { membershipsToUpdate.forEach(async (membership) => {
await updateSubscriptionOrgQuantity({ await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString() organizationId: membership.organization.toString(),
}); });
}); });
@@ -104,11 +104,11 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
{ {
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
}, },
{ {
user, user,
status: ACCEPTED status: ACCEPTED,
} }
); );
@@ -116,7 +116,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
token = tokens.token; token = tokens.token;
@@ -127,27 +127,27 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
"email": email, "email": email,
"eventName": "Sign Up", "eventName": "Sign Up",
"firstName": firstName, "firstName": firstName,
"lastName": lastName "lastName": lastName,
}, { }, {
headers: { headers: {
"Accept": "application/json", "Accept": "application/json",
"Authorization": "Bearer " + (await getLoopsApiKey()) "Authorization": "Bearer " + (await getLoopsApiKey()),
}, },
}); });
} }
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully set up account', message: "Successfully set up account",
user, user,
token token,
}); });
}; };
@@ -172,7 +172,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
} = req.body; } = req.body;
// get user // get user
@@ -182,16 +182,16 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
// case 1: user doesn't exist. // case 1: user doesn't exist.
// case 2: user has already completed account // case 2: user has already completed account
return res.status(403).send({ return res.status(403).send({
error: 'Failed to complete account for complete user' error: "Failed to complete account for complete user",
}); });
} }
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
}); });
if (!membershipOrg) throw new Error('Failed to find invitations for email'); if (!membershipOrg) throw new Error("Failed to find invitations for email");
// complete setting up user's account // complete setting up user's account
user = await completeAccount({ user = await completeAccount({
@@ -207,33 +207,33 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
}); });
if (!user) if (!user)
throw new Error('Failed to complete account for non-existent user'); throw new Error("Failed to complete account for non-existent user");
// update organization membership statuses that are // update organization membership statuses that are
// invited to completed with user attached // invited to completed with user attached
const membershipsToUpdate = await MembershipOrg.find({ const membershipsToUpdate = await MembershipOrg.find({
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
}); });
membershipsToUpdate.forEach(async (membership) => { membershipsToUpdate.forEach(async (membership) => {
await updateSubscriptionOrgQuantity({ await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString() organizationId: membership.organization.toString(),
}); });
}); });
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
{ {
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
}, },
{ {
user, user,
status: ACCEPTED status: ACCEPTED,
} }
); );
@@ -241,22 +241,22 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
token = tokens.token; token = tokens.token;
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully set up account', message: "Successfully set up account",
user, user,
token token,
}); });
}; };
+49 -55
View File
@@ -1,71 +1,65 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import { Membership, Secret } from "../../models";
Membership, Secret, import Tag, { ITag } from "../../models/tag";
} from '../../models'; import { Builder } from "builder-pattern";
import Tag, { ITag } from '../../models/tag'; import to from "await-to-js";
import { Builder } from "builder-pattern" import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import to from 'await-to-js'; import { MongoError } from "mongodb";
import { BadRequestError, UnauthorizedRequestError } from '../../utils/errors';
import { MongoError } from 'mongodb';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
export const createWorkspaceTag = async (req: Request, res: Response) => { export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params const { workspaceId } = req.params;
const { name, slug } = req.body const { name, slug } = req.body;
const sanitizedTagToCreate = Builder<ITag>() const sanitizedTagToCreate = Builder<ITag>()
.name(name) .name(name)
.workspace(new Types.ObjectId(workspaceId)) .workspace(new Types.ObjectId(workspaceId))
.slug(slug) .slug(slug)
.user(new Types.ObjectId(req.user._id)) .user(new Types.ObjectId(req.user._id))
.build(); .build();
const [err, createdTag] = await to(Tag.create(sanitizedTagToCreate)) const [err, createdTag] = await to(Tag.create(sanitizedTagToCreate));
if (err) { if (err) {
if ((err as MongoError).code === 11000) { if ((err as MongoError).code === 11000) {
throw BadRequestError({ message: "Tags must be unique in a workspace" }) throw BadRequestError({ message: "Tags must be unique in a workspace" });
} }
throw err throw err;
} }
res.json(createdTag) res.json(createdTag);
} };
export const deleteWorkspaceTag = async (req: Request, res: Response) => { export const deleteWorkspaceTag = async (req: Request, res: Response) => {
const { tagId } = req.params const { tagId } = req.params;
const tagFromDB = await Tag.findById(tagId) const tagFromDB = await Tag.findById(tagId);
if (!tagFromDB) { if (!tagFromDB) {
throw BadRequestError() throw BadRequestError();
} }
// can only delete if the request user is one that belongs to the same workspace as the tag // can only delete if the request user is one that belongs to the same workspace as the tag
const membership = await Membership.findOne({ const membership = await Membership.findOne({
user: req.user, user: req.user,
workspace: tagFromDB.workspace workspace: tagFromDB.workspace
}); });
if (!membership) { if (!membership) {
UnauthorizedRequestError({ message: 'Failed to validate membership' }); UnauthorizedRequestError({ message: "Failed to validate membership" });
} }
const result = await Tag.findByIdAndDelete(tagId); const result = await Tag.findByIdAndDelete(tagId);
// remove the tag from secrets // remove the tag from secrets
await Secret.updateMany( await Secret.updateMany({ tags: { $in: [tagId] } }, { $pull: { tags: tagId } });
{ tags: { $in: [tagId] } },
{ $pull: { tags: tagId } }
);
res.json(result); res.json(result);
} };
export const getWorkspaceTags = async (req: Request, res: Response) => { export const getWorkspaceTags = async (req: Request, res: Response) => {
const { workspaceId } = req.params const { workspaceId } = req.params;
const workspaceTags = await Tag.find({ workspace: workspaceId }) const workspaceTags = await Tag.find({ workspace: workspaceId });
return res.json({ return res.json({
workspaceTags workspaceTags
}) });
} };
+10 -10
View File
@@ -1,8 +1,8 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { import {
MembershipOrg,
User, User,
MembershipOrg } from "../../models";
} from '../../models';
/** /**
* Return the current user. * Return the current user.
@@ -38,10 +38,10 @@ export const getMe = async (req: Request, res: Response) => {
*/ */
const user = await User const user = await User
.findById(req.user._id) .findById(req.user._id)
.select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag'); .select("+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag");
return res.status(200).send({ return res.status(200).send({
user user,
}); });
} }
@@ -60,7 +60,7 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
if (isMfaEnabled) { if (isMfaEnabled) {
// TODO: adapt this route/controller // TODO: adapt this route/controller
// to work for different forms of MFA // to work for different forms of MFA
req.user.mfaMethods = ['email']; req.user.mfaMethods = ["email"];
} else { } else {
req.user.mfaMethods = []; req.user.mfaMethods = [];
} }
@@ -70,7 +70,7 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
const user = req.user; const user = req.user;
return res.status(200).send({ return res.status(200).send({
user user,
}); });
} }
@@ -109,11 +109,11 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
*/ */
const organizations = ( const organizations = (
await MembershipOrg.find({ await MembershipOrg.find({
user: req.user._id user: req.user._id,
}).populate('organization') }).populate("organization")
).map((m) => m.organization); ).map((m) => m.organization);
return res.status(200).send({ return res.status(200).send({
organizations organizations,
}); });
} }
@@ -1,25 +1,19 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import {
Workspace,
Secret,
Membership,
MembershipOrg,
Integration,
IntegrationAuth,
Key, Key,
IUser, Membership,
ServiceToken, ServiceTokenData,
ServiceTokenData Workspace,
} from '../../models'; } from "../../models";
import { import {
v2PushSecrets as push,
pullSecrets as pull, pullSecrets as pull,
reformatPullSecrets v2PushSecrets as push,
} from '../../helpers/secret'; reformatPullSecrets,
import { pushKeys } from '../../helpers/key'; } from "../../helpers/secret";
import { TelemetryService, EventService } from '../../services'; import { pushKeys } from "../../helpers/key";
import { eventPushSecrets } from '../../events'; import { EventService, TelemetryService } from "../../services";
import { eventPushSecrets } from "../../events";
interface V2PushSecret { interface V2PushSecret {
type: string; // personal or shared type: string; // personal or shared
@@ -54,12 +48,12 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// validate environment // validate environment
const workspaceEnvs = req.membership.workspace.environments; const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment'); throw new Error("Failed to validate environment");
} }
// sanitize secrets // sanitize secrets
secrets = secrets.filter( secrets = secrets.filter(
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== '' (s: V2PushSecret) => s.secretKeyCiphertext !== "" && s.secretValueCiphertext !== ""
); );
await push({ await push({
@@ -67,26 +61,26 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
secrets, secrets,
channel: channel ? channel : 'cli', channel: channel ? channel : "cli",
ipAddress: req.realIP ipAddress: req.realIP,
}); });
await pushKeys({ await pushKeys({
userId: req.user._id, userId: req.user._id,
workspaceId, workspaceId,
keys keys,
}); });
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'secrets pushed', event: "secrets pushed",
distinctId: req.user.email, distinctId: req.user.email,
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
environment, environment,
workspaceId, workspaceId,
channel: channel ? channel : 'cli' channel: channel ? channel : "cli",
} },
}); });
} }
@@ -94,12 +88,12 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
EventService.handleEvent({ EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment environment,
}) }),
}); });
return res.status(200).send({ return res.status(200).send({
message: 'Successfully uploaded workspace secrets' message: "Successfully uploaded workspace secrets",
}); });
}; };
@@ -126,18 +120,18 @@ export const pullSecrets = async (req: Request, res: Response) => {
// validate environment // validate environment
const workspaceEnvs = req.membership.workspace.environments; const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) { if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment'); throw new Error("Failed to validate environment");
} }
secrets = await pull({ secrets = await pull({
userId, userId,
workspaceId, workspaceId,
environment, environment,
channel: channel ? channel : 'cli', channel: channel ? channel : "cli",
ipAddress: req.realIP ipAddress: req.realIP,
}); });
if (channel !== 'cli') { if (channel !== "cli") {
secrets = reformatPullSecrets({ secrets }); secrets = reformatPullSecrets({ secrets });
} }
@@ -145,18 +139,18 @@ export const pullSecrets = async (req: Request, res: Response) => {
// capture secrets pushed event in production // capture secrets pushed event in production
postHogClient.capture({ postHogClient.capture({
distinctId: req.user.email, distinctId: req.user.email,
event: 'secrets pulled', event: "secrets pulled",
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
environment, environment,
workspaceId, workspaceId,
channel: channel ? channel : 'cli' channel: channel ? channel : "cli",
} },
}); });
} }
return res.status(200).send({ return res.status(200).send({
secrets secrets,
}); });
}; };
@@ -194,10 +188,10 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
key = await Key.findOne({ key = await Key.findOne({
workspace: workspaceId, workspace: workspaceId,
receiver: req.user._id receiver: req.user._id,
}).populate('sender', '+publicKey'); }).populate("sender", "+publicKey");
if (!key) throw new Error('Failed to find workspace key'); if (!key) throw new Error("Failed to find workspace key");
return res.status(200).json(key); return res.status(200).json(key);
} }
@@ -209,12 +203,12 @@ export const getWorkspaceServiceTokenData = async (
const serviceTokenData = await ServiceTokenData const serviceTokenData = await ServiceTokenData
.find({ .find({
workspace: workspaceId workspace: workspaceId,
}) })
.select('+encryptedKey +iv +tag'); .select("+encryptedKey +iv +tag");
return res.status(200).send({ return res.status(200).send({
serviceTokenData serviceTokenData,
}); });
} }
@@ -261,11 +255,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
const memberships = await Membership.find({ const memberships = await Membership.find({
workspace: workspaceId workspace: workspaceId,
}).populate('user', '+publicKey'); }).populate("user", "+publicKey");
return res.status(200).send({ return res.status(200).send({
memberships memberships,
}); });
} }
@@ -330,21 +324,21 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
} }
*/ */
const { const {
membershipId membershipId,
} = req.params; } = req.params;
const { role } = req.body; const { role } = req.body;
const membership = await Membership.findByIdAndUpdate( const membership = await Membership.findByIdAndUpdate(
membershipId, membershipId,
{ {
role role,
}, { }, {
new: true new: true,
} }
); );
return res.status(200).send({ return res.status(200).send({
membership membership,
}); });
} }
@@ -392,20 +386,20 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
} }
*/ */
const { const {
membershipId membershipId,
} = req.params; } = req.params;
const membership = await Membership.findByIdAndDelete(membershipId); const membership = await Membership.findByIdAndDelete(membershipId);
if (!membership) throw new Error('Failed to delete workspace membership'); if (!membership) throw new Error("Failed to delete workspace membership");
await Key.deleteMany({ await Key.deleteMany({
receiver: membership.user, receiver: membership.user,
workspace: membership.workspace workspace: membership.workspace,
}); });
return res.status(200).send({ return res.status(200).send({
membership membership,
}); });
} }
@@ -421,18 +415,18 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
const workspace = await Workspace.findOneAndUpdate( const workspace = await Workspace.findOneAndUpdate(
{ {
_id: workspaceId _id: workspaceId,
}, },
{ {
autoCapitalization autoCapitalization,
}, },
{ {
new: true new: true,
} }
); );
return res.status(200).send({ return res.status(200).send({
message: 'Successfully changed autoCapitalization setting', message: "Successfully changed autoCapitalization setting",
workspace workspace,
}); });
}; };
+50 -50
View File
@@ -1,29 +1,29 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express'; import { Request, Response } from "express";
import jwt from 'jsonwebtoken'; import jwt from "jsonwebtoken";
import * as Sentry from '@sentry/node'; import * as Sentry from "@sentry/node";
import * as bigintConversion from 'bigint-conversion'; import * as bigintConversion from "bigint-conversion";
const jsrp = require('jsrp'); const jsrp = require("jsrp");
import { User, LoginSRPDetail } from '../../models'; import { LoginSRPDetail, User } from "../../models";
import { issueAuthTokens, createToken, validateProviderAuthToken } from '../../helpers/auth'; import { createToken, issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { checkUserDevice } from '../../helpers/user'; import { checkUserDevice } from "../../helpers/user";
import { sendMail } from '../../helpers/nodemailer'; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from '../../services'; import { TokenService } from "../../services";
import { EELogService } from '../../ee/services'; import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from '../../utils/errors'; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { import {
ACTION_LOGIN,
TOKEN_EMAIL_MFA, TOKEN_EMAIL_MFA,
ACTION_LOGIN } from "../../variables";
} from '../../variables'; import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
import { import {
getHttpsEnabled,
getJwtMfaLifetime, getJwtMfaLifetime,
getJwtMfaSecret, getJwtMfaSecret,
getHttpsEnabled, } from "../../config";
} from '../../config'; import { AuthProvider } from "../../models/user";
import { AuthProvider } from '../../models/user';
declare module 'jsonwebtoken' { declare module "jsonwebtoken" {
export interface ProviderAuthJwtPayload extends jwt.JwtPayload { export interface ProviderAuthJwtPayload extends jwt.JwtPayload {
userId: string; userId: string;
email: string; email: string;
@@ -43,7 +43,7 @@ export const login1 = async (req: Request, res: Response) => {
const { const {
email, email,
providerAuthToken, providerAuthToken,
clientPublicKey clientPublicKey,
}: { }: {
email: string; email: string;
clientPublicKey: string, clientPublicKey: string,
@@ -52,9 +52,9 @@ export const login1 = async (req: Request, res: Response) => {
const user = await User.findOne({ const user = await User.findOne({
email, email,
}).select('+salt +verifier'); }).select("+salt +verifier");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
if (user.authProvider) { if (user.authProvider) {
await validateProviderAuthToken({ await validateProviderAuthToken({
@@ -68,13 +68,13 @@ export const login1 = async (req: Request, res: Response) => {
server.init( server.init(
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier verifier: user.verifier,
}, },
async () => { async () => {
// generate server-side public key // generate server-side public key
const serverPublicKey = server.getPublicKey(); const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ await LoginSRPDetail.findOneAndReplace({
email: email email: email,
}, { }, {
email, email,
userId: user.id, userId: user.id,
@@ -84,7 +84,7 @@ export const login1 = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
serverPublicKey, serverPublicKey,
salt: user.salt salt: user.salt,
}); });
} }
); );
@@ -92,7 +92,7 @@ export const login1 = async (req: Request, res: Response) => {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: 'Failed to start authentication process' message: "Failed to start authentication process",
}); });
} }
}; };
@@ -107,15 +107,15 @@ export const login1 = async (req: Request, res: Response) => {
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
try { try {
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
const { email, clientProof, providerAuthToken } = req.body; const { email, clientProof, providerAuthToken } = req.body;
const user = await User.findOne({ const user = await User.findOne({
email, email,
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices'); }).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error("Failed to find user");
if (user.authProvider) { if (user.authProvider) {
await validateProviderAuthToken({ await validateProviderAuthToken({
@@ -136,7 +136,7 @@ export const login2 = async (req: Request, res: Response) => {
{ {
salt: user.salt, salt: user.salt,
verifier: user.verifier, verifier: user.verifier,
b: loginSRPDetail.serverBInt b: loginSRPDetail.serverBInt,
}, },
async () => { async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey); server.setClientPublicKey(loginSRPDetail.clientPublicKey);
@@ -150,52 +150,52 @@ export const login2 = async (req: Request, res: Response) => {
// generate temporary MFA token // generate temporary MFA token
const token = createToken({ const token = createToken({
payload: { payload: {
userId: user._id.toString() userId: user._id.toString(),
}, },
expiresIn: await getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret() secret: await getJwtMfaSecret(),
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email,
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: "emailMfa.handlebars",
subjectLine: 'Infisical MFA code', subjectLine: "Infisical MFA code",
recipients: [user.email], recipients: [user.email],
substitutions: { substitutions: {
code code,
} },
}); });
return res.status(200).send({ return res.status(200).send({
mfaEnabled: true, mfaEnabled: true,
token token,
}); });
} }
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
// case: user does not have MFA enablgged // case: user does not have MFA enablgged
@@ -221,7 +221,7 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey, publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey, encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag tag: user.tag,
} }
if ( if (
@@ -236,21 +236,21 @@ export const login2 = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({ const loginAction = await EELogService.createAction({
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id userId: user._id,
}); });
loginAction && await EELogService.createLog({ loginAction && await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP ipAddress: req.realIP,
}); });
return res.status(200).send(response); return res.status(200).send(response);
} }
return res.status(400).send({ return res.status(400).send({
message: 'Failed to authenticate. Try again?' message: "Failed to authenticate. Try again?",
}); });
} }
); );
@@ -258,7 +258,7 @@ export const login2 = async (req: Request, res: Response) => {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: 'Failed to authenticate. Try again?' message: "Failed to authenticate. Try again?",
}); });
} }
}; };
+4 -4
View File
@@ -1,7 +1,7 @@
import * as secretsController from './secretsController'; import * as secretsController from "./secretsController";
import * as workspacesController from './workspacesController'; import * as workspacesController from "./workspacesController";
import * as authController from './authController'; import * as authController from "./authController";
import * as signupController from './signupController'; import * as signupController from "./signupController";
export { export {
authController, authController,
+25 -25
View File
@@ -1,6 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { SecretService, EventService } from "../../services"; import { EventService, SecretService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { BotService } from "../../services"; import { BotService } from "../../services";
import { repackageSecretToRaw } from "../../helpers/secrets"; import { repackageSecretToRaw } from "../../helpers/secrets";
@@ -25,18 +25,18 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
}); });
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
return res.status(200).send({ return res.status(200).send({
secrets: secrets.map((secret) => { secrets: secrets.map((secret) => {
const rep = repackageSecretToRaw({ const rep = repackageSecretToRaw({
secret, secret,
key key,
}); });
return rep; return rep;
}) }),
}); });
}; };
@@ -62,14 +62,14 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
}); });
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
return res.status(200).send({ return res.status(200).send({
secret: repackageSecretToRaw({ secret: repackageSecretToRaw({
secret, secret,
key key,
}) }),
}); });
}; };
@@ -86,26 +86,26 @@ export const createSecretRaw = async (req: Request, res: Response) => {
type, type,
secretValue, secretValue,
secretComment, secretComment,
secretPath = "/" secretPath = "/",
} = req.body; } = req.body;
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({ const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretName, plaintext: secretName,
key key,
}); });
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({ const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretValue, plaintext: secretValue,
key key,
}); });
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({ const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretComment, plaintext: secretComment,
key key,
}); });
const secret = await SecretService.createSecret({ const secret = await SecretService.createSecret({
@@ -123,7 +123,7 @@ export const createSecretRaw = async (req: Request, res: Response) => {
secretPath, secretPath,
secretCommentCiphertext: secretCommentEncrypted.ciphertext, secretCommentCiphertext: secretCommentEncrypted.ciphertext,
secretCommentIV: secretCommentEncrypted.iv, secretCommentIV: secretCommentEncrypted.iv,
secretCommentTag: secretCommentEncrypted.tag secretCommentTag: secretCommentEncrypted.tag,
}); });
await EventService.handleEvent({ await EventService.handleEvent({
@@ -139,8 +139,8 @@ export const createSecretRaw = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
secret: repackageSecretToRaw({ secret: repackageSecretToRaw({
secret: secretWithoutBlindIndex, secret: secretWithoutBlindIndex,
key key,
}) }),
}); });
} }
@@ -160,12 +160,12 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
} = req.body; } = req.body;
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({ const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretValue, plaintext: secretValue,
key key,
}); });
const secret = await SecretService.updateSecret({ const secret = await SecretService.updateSecret({
@@ -190,8 +190,8 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
secret: repackageSecretToRaw({ secret: repackageSecretToRaw({
secret, secret,
key key,
}) }),
}); });
}; };
@@ -206,7 +206,7 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
type, type,
secretPath = "/" secretPath = "/",
} = req.body; } = req.body;
const { secret } = await SecretService.deleteSecret({ const { secret } = await SecretService.deleteSecret({
@@ -226,14 +226,14 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
}); });
const key = await BotService.getWorkspaceKeyWithBot({ const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
return res.status(200).send({ return res.status(200).send({
secret: repackageSecretToRaw({ secret: repackageSecretToRaw({
secret, secret,
key key,
}) }),
}); });
}; };
@@ -324,7 +324,7 @@ export const createSecret = async (req: Request, res: Response) => {
secretPath, secretPath,
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag secretCommentTag,
}); });
await EventService.handleEvent({ await EventService.handleEvent({
@@ -395,7 +395,7 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
workspaceId, workspaceId,
environment, environment,
type, type,
secretPath = "/" secretPath = "/",
} = req.body; } = req.body;
const { secret } = await SecretService.deleteSecret({ const { secret } = await SecretService.deleteSecret({
+36 -36
View File
@@ -1,17 +1,17 @@
import jwt from 'jsonwebtoken'; import jwt from "jsonwebtoken";
import { Request, Response } from 'express'; import { Request, Response } from "express";
import * as Sentry from '@sentry/node'; import * as Sentry from "@sentry/node";
import { User, MembershipOrg } from '../../models'; import { MembershipOrg, User } from "../../models";
import { completeAccount } from '../../helpers/user'; import { completeAccount } from "../../helpers/user";
import { import {
initializeDefaultOrg initializeDefaultOrg,
} from '../../helpers/signup'; } from "../../helpers/signup";
import { issueAuthTokens, validateProviderAuthToken } from '../../helpers/auth'; import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { INVITED, ACCEPTED } from '../../variables'; import { ACCEPTED, INVITED } from "../../variables";
import { standardRequest } from '../../config/request'; import { standardRequest } from "../../config/request";
import { getLoopsApiKey, getHttpsEnabled, getJwtSignupSecret } from '../../config'; import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
import { BadRequestError } from '../../utils/errors'; import { BadRequestError } from "../../utils/errors";
import { TelemetryService } from '../../services'; import { TelemetryService } from "../../services";
/** /**
* Complete setting up user by adding their personal and auth information as part of the * Complete setting up user by adding their personal and auth information as part of the
@@ -63,7 +63,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// case 1: user doesn't exist. // case 1: user doesn't exist.
// case 2: user has already completed account // case 2: user has already completed account
return res.status(403).send({ return res.status(403).send({
error: 'Failed to complete account for complete user' error: "Failed to complete account for complete user",
}); });
} }
@@ -74,16 +74,16 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
user, user,
}); });
} else { } else {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null] const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null]
if (AUTH_TOKEN_TYPE === null) { if (AUTH_TOKEN_TYPE === null) {
throw BadRequestError({ message: `Missing Authorization Header in the request header.` }); throw BadRequestError({ message: "Missing Authorization Header in the request header." });
} }
if (AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') { if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") {
throw BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` }) throw BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` })
} }
if (AUTH_TOKEN_VALUE === null) { if (AUTH_TOKEN_VALUE === null) {
throw BadRequestError({ throw BadRequestError({
message: 'Missing Authorization Body in the request header', message: "Missing Authorization Body in the request header",
}) })
} }
@@ -110,16 +110,16 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
}); });
if (!user) if (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
// initialize default organization and workspace // initialize default organization and workspace
await initializeDefaultOrg({ await initializeDefaultOrg({
organizationName, organizationName,
user user,
}); });
// update organization membership statuses that are // update organization membership statuses that are
@@ -127,11 +127,11 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
{ {
inviteEmail: email, inviteEmail: email,
status: INVITED status: INVITED,
}, },
{ {
user, user,
status: ACCEPTED status: ACCEPTED,
} }
); );
@@ -139,7 +139,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
const tokens = await issueAuthTokens({ const tokens = await issueAuthTokens({
userId: user._id, userId: user._id,
ip: req.realIP, ip: req.realIP,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers["user-agent"] ?? "",
}); });
token = tokens.token; token = tokens.token;
@@ -150,45 +150,45 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
"email": email, "email": email,
"eventName": "Sign Up", "eventName": "Sign Up",
"firstName": firstName, "firstName": firstName,
"lastName": lastName "lastName": lastName,
}, { }, {
headers: { headers: {
"Accept": "application/json", "Accept": "application/json",
"Authorization": "Bearer " + (await getLoopsApiKey()) "Authorization": "Bearer " + (await getLoopsApiKey()),
}, },
}); });
} }
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie("jid", tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: "/",
sameSite: 'strict', sameSite: "strict",
secure: await getHttpsEnabled() secure: await getHttpsEnabled(),
}); });
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: 'User Signed Up', event: "User Signed Up",
distinctId: email, distinctId: email,
properties: { properties: {
email, email,
attributionSource attributionSource,
} },
}); });
} }
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: 'Failed to complete account setup' message: "Failed to complete account setup",
}); });
} }
return res.status(200).send({ return res.status(200).send({
message: 'Successfully set up account', message: "Successfully set up account",
user, user,
token token,
}); });
}; };
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { Secret } from '../../models'; import { Secret } from "../../models";
import { SecretService } from'../../services'; import { SecretService } from"../../services";
/** /**
* Return whether or not all secrets in workspace with id [workspaceId] * Return whether or not all secrets in workspace with id [workspaceId]
@@ -16,8 +16,8 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response)
const secretsWithoutBlindIndex = await Secret.countDocuments({ const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
secretBlindIndex: { secretBlindIndex: {
$exists: false $exists: false,
} },
}); });
return res.status(200).send(secretsWithoutBlindIndex === 0); return res.status(200).send(secretsWithoutBlindIndex === 0);
@@ -30,11 +30,11 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
const secrets = await Secret.find({ const secrets = await Secret.find({
workspace: new Types.ObjectId (workspaceId) workspace: new Types.ObjectId (workspaceId),
}); });
return res.status(200).send({ return res.status(200).send({
secrets secrets,
}); });
} }
@@ -51,14 +51,14 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params; const { workspaceId } = req.params;
const { const {
secretsToUpdate secretsToUpdate,
}: { }: {
secretsToUpdate: SecretToUpdate[]; secretsToUpdate: SecretToUpdate[];
} = req.body; } = req.body;
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId),
}); });
// update secret blind indices // update secret blind indices
@@ -66,18 +66,18 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
secretsToUpdate.map(async (secretToUpdate: SecretToUpdate) => { secretsToUpdate.map(async (secretToUpdate: SecretToUpdate) => {
const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({ const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
secretName: secretToUpdate.secretName, secretName: secretToUpdate.secretName,
salt salt,
}); });
return ({ return ({
updateOne: { updateOne: {
filter: { filter: {
_id: new Types.ObjectId(secretToUpdate._id) _id: new Types.ObjectId(secretToUpdate._id),
}, },
update: { update: {
secretBlindIndex secretBlindIndex,
} },
} },
}); });
}) })
); );
@@ -85,6 +85,6 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
await Secret.bulkWrite(operations); await Secret.bulkWrite(operations);
return res.status(200).send({ return res.status(200).send({
message: 'Successfully named workspace secrets' message: "Successfully named workspace secrets",
}); });
} }
@@ -1,6 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { Action, SecretVersion } from '../../models'; import { Action } from "../../models";
import { ActionNotFoundError } from '../../../utils/errors'; import { ActionNotFoundError } from "../../../utils/errors";
export const getAction = async (req: Request, res: Response) => { export const getAction = async (req: Request, res: Response) => {
let action; let action;
@@ -10,21 +10,21 @@ export const getAction = async (req: Request, res: Response) => {
action = await Action action = await Action
.findById(actionId) .findById(actionId)
.populate([ .populate([
'payload.secretVersions.oldSecretVersion', "payload.secretVersions.oldSecretVersion",
'payload.secretVersions.newSecretVersion' "payload.secretVersions.newSecretVersion",
]); ]);
if (!action) throw ActionNotFoundError({ if (!action) throw ActionNotFoundError({
message: 'Failed to find action' message: "Failed to find action",
}); });
} catch (err) { } catch (err) {
throw ActionNotFoundError({ throw ActionNotFoundError({
message: 'Failed to find action' message: "Failed to find action",
}); });
} }
return res.status(200).send({ return res.status(200).send({
action action,
}); });
} }
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { EELicenseService } from '../../services'; import { EELicenseService } from "../../services";
import { getLicenseServerUrl } from '../../../config'; import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from '../../../config/request'; import { licenseServerKeyRequest } from "../../../config/request";
/** /**
* Return available cloud product information. * Return available cloud product information.
@@ -11,9 +11,9 @@ import { licenseServerKeyRequest } from '../../../config/request';
* @returns * @returns
*/ */
export const getCloudProducts = async (req: Request, res: Response) => { export const getCloudProducts = async (req: Request, res: Response) => {
const billingCycle = req.query['billing-cycle'] as string; const billingCycle = req.query["billing-cycle"] as string;
if (EELicenseService.instanceType === 'cloud') { if (EELicenseService.instanceType === "cloud") {
const { data } = await licenseServerKeyRequest.get( const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` `${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
); );
@@ -23,6 +23,6 @@ export const getCloudProducts = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
head: [], head: [],
rows: [] rows: [],
}); });
} }
+9 -9
View File
@@ -1,11 +1,11 @@
import * as stripeController from './stripeController'; import * as stripeController from "./stripeController";
import * as secretController from './secretController'; import * as secretController from "./secretController";
import * as secretSnapshotController from './secretSnapshotController'; import * as secretSnapshotController from "./secretSnapshotController";
import * as organizationsController from './organizationsController'; import * as organizationsController from "./organizationsController";
import * as workspaceController from './workspaceController'; import * as workspaceController from "./workspaceController";
import * as actionController from './actionController'; import * as actionController from "./actionController";
import * as membershipController from './membershipController'; import * as membershipController from "./membershipController";
import * as cloudProductsController from './cloudProductsController'; import * as cloudProductsController from "./cloudProductsController";
export { export {
stripeController, stripeController,
@@ -15,5 +15,5 @@ export {
workspaceController, workspaceController,
actionController, actionController,
membershipController, membershipController,
cloudProductsController cloudProductsController,
} }
@@ -3,7 +3,7 @@ import { Membership, Workspace } from "../../../models";
import { IMembershipPermission } from "../../../models/membership"; import { IMembershipPermission } from "../../../models/membership";
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
import { ADMIN, MEMBER } from "../../../variables/organization"; import { ADMIN, MEMBER } from "../../../variables/organization";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../../variables'; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../../variables";
import { Builder } from "builder-pattern" import { Builder } from "builder-pattern"
import _ from "lodash"; import _ from "lodash";
@@ -39,7 +39,7 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
throw BadRequestError({ message: "Something went wrong when locating the related workspace" }) throw BadRequestError({ message: "Something went wrong when locating the related workspace" })
} }
const uniqueEnvironmentSlugs = new Set(_.uniq(_.map(relatedWorkspace.environments, 'slug'))); const uniqueEnvironmentSlugs = new Set(_.uniq(_.map(relatedWorkspace.environments, "slug")));
sanitizedMembershipPermissionsUnique.forEach(permission => { sanitizedMembershipPermissionsUnique.forEach(permission => {
if (!uniqueEnvironmentSlugs.has(permission.environmentSlug)) { if (!uniqueEnvironmentSlugs.has(permission.environmentSlug)) {
@@ -59,6 +59,6 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
} }
res.send({ res.send({
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions permissionsDenied: updatedMembershipWithPermissions.deniedPermissions,
}) })
} }
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import { getLicenseServerUrl } from '../../../config'; import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from '../../../config/request'; import { licenseServerKeyRequest } from "../../../config/request";
import { EELicenseService } from '../../services'; import { EELicenseService } from "../../services";
/** /**
* Return the organization's current plan and allowed feature set * Return the organization's current plan and allowed feature set
@@ -25,13 +25,13 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
*/ */
export const updateOrganizationPlan = async (req: Request, res: Response) => { export const updateOrganizationPlan = async (req: Request, res: Response) => {
const { const {
productId productId,
} = req.body; } = req.body;
const { data } = await licenseServerKeyRequest.patch( const { data } = await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan`,
{ {
productId productId,
} }
); );
@@ -47,7 +47,7 @@ export const getOrganizationPmtMethods = async (req: Request, res: Response) =>
); );
return res.status(200).send({ return res.status(200).send({
pmtMethods pmtMethods,
}); });
} }
@@ -57,19 +57,19 @@ export const getOrganizationPmtMethods = async (req: Request, res: Response) =>
export const addOrganizationPmtMethod = async (req: Request, res: Response) => { export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
const { const {
success_url, success_url,
cancel_url cancel_url,
} = req.body; } = req.body;
const { data: { url } } = await licenseServerKeyRequest.post( const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
{ {
success_url, success_url,
cancel_url cancel_url,
} }
); );
return res.status(200).send({ return res.status(200).send({
url url,
}); });
} }
@@ -17,11 +17,11 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId) const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId)
.lean() .lean()
.populate<{ secretVersions: ISecretVersion[] }>({ .populate<{ secretVersions: ISecretVersion[] }>({
path: 'secretVersions', path: "secretVersions",
populate: { populate: {
path: 'tags', path: "tags",
model: 'Tag' model: "Tag",
} },
}) })
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion"); .populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
@@ -1,6 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from "express";
import Stripe from 'stripe'; import Stripe from "stripe";
import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config'; import { getStripeSecretKey, getStripeWebhookSecret } from "../../../config";
/** /**
* Handle service provisioning/un-provisioning via Stripe * Handle service provisioning/un-provisioning via Stripe
@@ -10,11 +10,11 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
*/ */
export const handleWebhook = async (req: Request, res: Response) => { export const handleWebhook = async (req: Request, res: Response) => {
const stripe = new Stripe(await getStripeSecretKey(), { const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01' apiVersion: "2022-08-01",
}); });
// check request for valid stripe signature // check request for valid stripe signature
const sig = req.headers['stripe-signature'] as string; const sig = req.headers["stripe-signature"] as string;
const event = stripe.webhooks.constructEvent( const event = stripe.webhooks.constructEvent(
req.body, req.body,
sig, sig,
@@ -22,7 +22,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
); );
switch (event.type) { switch (event.type) {
case '': case "":
break; break;
default: default:
} }
@@ -2,11 +2,11 @@ import { Request, Response } from "express";
import { PipelineStage, Types } from "mongoose"; import { PipelineStage, Types } from "mongoose";
import { Secret } from "../../../models"; import { Secret } from "../../../models";
import { import {
SecretSnapshot,
Log,
SecretVersion,
ISecretVersion,
FolderVersion, FolderVersion,
ISecretVersion,
Log,
SecretSnapshot,
SecretVersion,
TFolderRootVersionSchema, TFolderRootVersionSchema,
} from "../../models"; } from "../../models";
import { EESecretService } from "../../services"; import { EESecretService } from "../../services";
+25 -25
View File
@@ -1,17 +1,17 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { Action } from '../models'; import { Action } from "../models";
import { import {
getLatestNSecretSecretVersionIds,
getLatestSecretVersionIds, getLatestSecretVersionIds,
getLatestNSecretSecretVersionIds } from "../helpers/secretVersion";
} from '../helpers/secretVersion';
import { import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN, ACTION_LOGIN,
ACTION_LOGOUT, ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
} from '../../variables'; } from "../../variables";
/** /**
* Create an (audit) action for updating secrets * Create an (audit) action for updating secrets
@@ -26,7 +26,7 @@ const createActionUpdateSecret = async ({
serviceAccountId, serviceAccountId,
serviceTokenDataId, serviceTokenDataId,
workspaceId, workspaceId,
secretIds secretIds,
}: { }: {
name: string; name: string;
userId?: Types.ObjectId; userId?: Types.ObjectId;
@@ -37,11 +37,11 @@ const createActionUpdateSecret = async ({
}) => { }) => {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({ const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds, secretIds,
n: 2 n: 2,
})) }))
.map((s) => ({ .map((s) => ({
oldSecretVersion: s.versions[0]._id, oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id newSecretVersion: s.versions[1]._id,
})); }));
const action = await new Action({ const action = await new Action({
@@ -51,8 +51,8 @@ const createActionUpdateSecret = async ({
serviceTokenData: serviceTokenDataId, serviceTokenData: serviceTokenDataId,
workspace: workspaceId, workspace: workspaceId,
payload: { payload: {
secretVersions: latestSecretVersions secretVersions: latestSecretVersions,
} },
}).save(); }).save();
return action; return action;
@@ -72,7 +72,7 @@ const createActionSecret = async ({
serviceAccountId, serviceAccountId,
serviceTokenDataId, serviceTokenDataId,
workspaceId, workspaceId,
secretIds secretIds,
}: { }: {
name: string; name: string;
userId?: Types.ObjectId; userId?: Types.ObjectId;
@@ -84,10 +84,10 @@ const createActionSecret = async ({
// case: action is adding, deleting, or reading secrets // case: action is adding, deleting, or reading secrets
// -> add new secret versions // -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({ const latestSecretVersions = (await getLatestSecretVersionIds({
secretIds secretIds,
})) }))
.map((s) => ({ .map((s) => ({
newSecretVersion: s.versionId newSecretVersion: s.versionId,
})); }));
const action = await new Action({ const action = await new Action({
@@ -97,8 +97,8 @@ const createActionSecret = async ({
serviceTokenData: serviceTokenDataId, serviceTokenData: serviceTokenDataId,
workspace: workspaceId, workspace: workspaceId,
payload: { payload: {
secretVersions: latestSecretVersions secretVersions: latestSecretVersions,
} },
}).save(); }).save();
return action; return action;
@@ -116,7 +116,7 @@ const createActionClient = ({
name, name,
userId, userId,
serviceAccountId, serviceAccountId,
serviceTokenDataId serviceTokenDataId,
}: { }: {
name: string; name: string;
userId?: Types.ObjectId; userId?: Types.ObjectId;
@@ -127,7 +127,7 @@ const createActionClient = ({
name, name,
user: userId, user: userId,
serviceAccount: serviceAccountId, serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId serviceTokenData: serviceTokenDataId,
}).save(); }).save();
return action; return action;
@@ -162,27 +162,27 @@ const createActionHelper = async ({
case ACTION_LOGOUT: case ACTION_LOGOUT:
action = await createActionClient({ action = await createActionClient({
name, name,
userId userId,
}); });
break; break;
case ACTION_ADD_SECRETS: case ACTION_ADD_SECRETS:
case ACTION_READ_SECRETS: case ACTION_READ_SECRETS:
case ACTION_DELETE_SECRETS: case ACTION_DELETE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret'); if (!workspaceId || !secretIds) throw new Error("Missing required params workspace id or secret ids to create action secret");
action = await createActionSecret({ action = await createActionSecret({
name, name,
userId, userId,
workspaceId, workspaceId,
secretIds secretIds,
}); });
break; break;
case ACTION_UPDATE_SECRETS: case ACTION_UPDATE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret'); if (!workspaceId || !secretIds) throw new Error("Missing required params workspace id or secret ids to create action secret");
action = await createActionUpdateSecret({ action = await createActionUpdateSecret({
name, name,
userId, userId,
workspaceId, workspaceId,
secretIds secretIds,
}); });
break; break;
} }
@@ -191,5 +191,5 @@ const createActionHelper = async ({
} }
export { export {
createActionHelper createActionHelper,
}; };
@@ -1,7 +1,7 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import _ from "lodash"; import _ from "lodash";
import { Membership } from "../../models"; import { Membership } from "../../models";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables'; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => { export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId }) const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
+6 -6
View File
@@ -1,8 +1,8 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import {
IAction,
Log, Log,
IAction } from "../models";
} from '../models';
/** /**
* Create an (audit) log * Create an (audit) log
@@ -21,7 +21,7 @@ const createLogHelper = async ({
workspaceId, workspaceId,
actions, actions,
channel, channel,
ipAddress ipAddress,
}: { }: {
userId?: Types.ObjectId; userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId; serviceAccountId?: Types.ObjectId;
@@ -39,12 +39,12 @@ const createLogHelper = async ({
actionNames: actions.map((a) => a.name), actionNames: actions.map((a) => a.name),
actions, actions,
channel, channel,
ipAddress ipAddress,
}).save(); }).save();
return log; return log;
} }
export { export {
createLogHelper createLogHelper,
} }
+3 -3
View File
@@ -1,10 +1,10 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Secret, ISecret } from "../../models"; import { Secret } from "../../models";
import { import {
FolderVersion,
ISecretVersion,
SecretSnapshot, SecretSnapshot,
SecretVersion, SecretVersion,
ISecretVersion,
FolderVersion,
} from "../models"; } from "../models";
/** /**
+3 -3
View File
@@ -1,7 +1,7 @@
import requireLicenseAuth from './requireLicenseAuth'; import requireLicenseAuth from "./requireLicenseAuth";
import requireSecretSnapshotAuth from './requireSecretSnapshotAuth'; import requireSecretSnapshotAuth from "./requireSecretSnapshotAuth";
export { export {
requireLicenseAuth, requireLicenseAuth,
requireSecretSnapshotAuth requireSecretSnapshotAuth,
} }
@@ -1,4 +1,4 @@
import { Request, Response, NextFunction } from 'express'; import { NextFunction, Request, Response } from "express";
/** /**
* Validate if organization hosting meets license requirements to * Validate if organization hosting meets license requirements to
@@ -7,7 +7,7 @@ import { Request, Response, NextFunction } from 'express';
* @param {String[]} obj.acceptedTiers * @param {String[]} obj.acceptedTiers
*/ */
const requireLicenseAuth = ({ const requireLicenseAuth = ({
acceptedTiers acceptedTiers,
}: { }: {
acceptedTiers: string[]; acceptedTiers: string[];
}) => { }) => {
@@ -1,9 +1,9 @@
import { Request, Response, NextFunction } from 'express'; import { NextFunction, Request, Response } from "express";
import { UnauthorizedRequestError, SecretSnapshotNotFoundError } from '../../utils/errors'; import { SecretSnapshotNotFoundError } from "../../utils/errors";
import { SecretSnapshot } from '../models'; import { SecretSnapshot } from "../models";
import { import {
validateMembership validateMembership,
} from '../../helpers/membership'; } from "../../helpers/membership";
/** /**
* Validate if user on request has proper membership for secret snapshot * Validate if user on request has proper membership for secret snapshot
@@ -15,7 +15,7 @@ import {
const requireSecretSnapshotAuth = ({ const requireSecretSnapshotAuth = ({
acceptedRoles, acceptedRoles,
}: { }: {
acceptedRoles: Array<'admin' | 'member'>; acceptedRoles: Array<"admin" | "member">;
}) => { }) => {
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
const { secretSnapshotId } = req.params; const { secretSnapshotId } = req.params;
@@ -24,14 +24,14 @@ const requireSecretSnapshotAuth = ({
if (!secretSnapshot) { if (!secretSnapshot) {
return next(SecretSnapshotNotFoundError({ return next(SecretSnapshotNotFoundError({
message: 'Failed to find secret snapshot' message: "Failed to find secret snapshot",
})); }));
} }
await validateMembership({ await validateMembership({
userId: req.user._id, userId: req.user._id,
workspaceId: secretSnapshot.workspace, workspaceId: secretSnapshot.workspace,
acceptedRoles acceptedRoles,
}); });
req.secretSnapshot = secretSnapshot as any; req.secretSnapshot = secretSnapshot as any;
+18 -18
View File
@@ -1,12 +1,12 @@
import { Schema, model, Types } from 'mongoose'; import { Schema, Types, model } from "mongoose";
import { import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN, ACTION_LOGIN,
ACTION_LOGOUT, ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS ACTION_UPDATE_SECRETS,
} from '../../variables'; } from "../../variables";
export interface IAction { export interface IAction {
name: string; name: string;
@@ -30,42 +30,42 @@ const actionSchema = new Schema<IAction>(
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS ACTION_DELETE_SECRETS,
] ],
}, },
user: { user: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'User' ref: "User",
}, },
serviceAccount: { serviceAccount: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'ServiceAccount' ref: "ServiceAccount",
}, },
serviceTokenData: { serviceTokenData: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'ServiceTokenData' ref: "ServiceTokenData",
}, },
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'Workspace' ref: "Workspace",
}, },
payload: { payload: {
secretVersions: [{ secretVersions: [{
oldSecretVersion: { oldSecretVersion: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'SecretVersion' ref: "SecretVersion",
}, },
newSecretVersion: { newSecretVersion: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'SecretVersion' ref: "SecretVersion",
} },
}] }],
} },
}, { }, {
timestamps: true timestamps: true,
} }
); );
const Action = model<IAction>('Action', actionSchema); const Action = model<IAction>("Action", actionSchema);
export default Action; export default Action;
+1 -1
View File
@@ -1,4 +1,4 @@
import { model, Schema, Types } from "mongoose"; import { Schema, Types, model } from "mongoose";
export type TFolderRootVersionSchema = { export type TFolderRootVersionSchema = {
_id: Types.ObjectId; _id: Types.ObjectId;
+19 -19
View File
@@ -1,12 +1,12 @@
import { Schema, model, Types } from 'mongoose'; import { Schema, Types, model } from "mongoose";
import { import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN, ACTION_LOGIN,
ACTION_LOGOUT, ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS ACTION_UPDATE_SECRETS,
} from '../../variables'; } from "../../variables";
export interface ILog { export interface ILog {
_id: Types.ObjectId; _id: Types.ObjectId;
@@ -24,19 +24,19 @@ const logSchema = new Schema<ILog>(
{ {
user: { user: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'User' ref: "User",
}, },
serviceAccount: { serviceAccount: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'ServiceAccount' ref: "ServiceAccount",
}, },
serviceTokenData: { serviceTokenData: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'ServiceTokenData' ref: "ServiceTokenData",
}, },
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'Workspace' ref: "Workspace",
}, },
actionNames: { actionNames: {
type: [String], type: [String],
@@ -46,28 +46,28 @@ const logSchema = new Schema<ILog>(
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS ACTION_DELETE_SECRETS,
], ],
required: true required: true,
}, },
actions: [{ actions: [{
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'Action', ref: "Action",
required: true required: true,
}], }],
channel: { channel: {
type: String, type: String,
enum: ['web', 'cli', 'auto', 'k8-operator', 'other'], enum: ["web", "cli", "auto", "k8-operator", "other"],
required: true required: true,
}, },
ipAddress: { ipAddress: {
type: String type: String,
} },
}, { }, {
timestamps: true timestamps: true,
} }
); );
const Log = model<ILog>('Log', logSchema); const Log = model<ILog>("Log", logSchema);
export default Log; export default Log;
+1 -1
View File
@@ -1,4 +1,4 @@
import { Schema, model, Types } from "mongoose"; import { Schema, Types, model } from "mongoose";
export interface ISecretSnapshot { export interface ISecretSnapshot {
workspace: Types.ObjectId; workspace: Types.ObjectId;
+6 -6
View File
@@ -1,10 +1,10 @@
import { Schema, model, Types } from "mongoose"; import { Schema, Types, model } from "mongoose";
import { import {
SECRET_SHARED,
SECRET_PERSONAL,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED,
} from "../../variables"; } from "../../variables";
export interface ISecretVersion { export interface ISecretVersion {
@@ -114,9 +114,9 @@ const secretVersionSchema = new Schema<ISecretVersion>(
required: true, required: true,
}, },
tags: { tags: {
ref: 'Tag', ref: "Tag",
type: [Schema.Types.ObjectId], type: [Schema.Types.ObjectId],
default: [] default: [],
}, },
}, },
{ {
+7 -7
View File
@@ -1,15 +1,15 @@
import express from 'express'; import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
validateRequest validateRequest,
} from '../../../middleware'; } from "../../../middleware";
import { param } from 'express-validator'; import { param } from "express-validator";
import { actionController } from '../../controllers/v1'; import { actionController } from "../../controllers/v1";
// TODO: put into action controller // TODO: put into action controller
router.get( router.get(
'/:actionId', "/:actionId",
param('actionId').exists().trim(), param("actionId").exists().trim(),
validateRequest, validateRequest,
actionController.getAction actionController.getAction
); );
+8 -8
View File
@@ -1,18 +1,18 @@
import express from 'express'; import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
requireAuth, requireAuth,
validateRequest validateRequest,
} from '../../../middleware'; } from "../../../middleware";
import { query } from 'express-validator'; import { query } from "express-validator";
import { cloudProductsController } from '../../controllers/v1'; import { cloudProductsController } from "../../controllers/v1";
router.get( router.get(
'/', "/",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
query('billing-cycle').exists().isIn(['monthly', 'yearly']), query("billing-cycle").exists().isIn(["monthly", "yearly"]),
validateRequest, validateRequest,
cloudProductsController.getCloudProducts cloudProductsController.getCloudProducts
); );
+7 -7
View File
@@ -1,9 +1,9 @@
import secret from './secret'; import secret from "./secret";
import secretSnapshot from './secretSnapshot'; import secretSnapshot from "./secretSnapshot";
import organizations from './organizations'; import organizations from "./organizations";
import workspace from './workspace'; import workspace from "./workspace";
import action from './action'; import action from "./action";
import cloudProducts from './cloudProducts'; import cloudProducts from "./cloudProducts";
export { export {
secret, secret,
@@ -11,5 +11,5 @@ export {
organizations, organizations,
workspace, workspace,
action, action,
cloudProducts cloudProducts,
} }
+31 -31
View File
@@ -1,86 +1,86 @@
import express from 'express'; import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
requireAuth, requireAuth,
requireOrganizationAuth, requireOrganizationAuth,
validateRequest validateRequest,
} from '../../../middleware'; } from "../../../middleware";
import { param, body, query } from 'express-validator'; import { body, param, query } from "express-validator";
import { organizationsController } from '../../controllers/v1'; import { organizationsController } from "../../controllers/v1";
import { import {
OWNER, ADMIN, MEMBER, ACCEPTED ACCEPTED, ADMIN, MEMBER, OWNER,
} from '../../../variables'; } from "../../../variables";
router.get( router.get(
'/:organizationId/plan', "/:organizationId/plan",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireOrganizationAuth({ requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER], acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED],
}), }),
param('organizationId').exists().trim(), param("organizationId").exists().trim(),
query('workspaceId').optional().isString(), query("workspaceId").optional().isString(),
validateRequest, validateRequest,
organizationsController.getOrganizationPlan organizationsController.getOrganizationPlan
); );
router.patch( router.patch(
'/:organizationId/plan', "/:organizationId/plan",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireOrganizationAuth({ requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER], acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED],
}), }),
param('organizationId').exists().trim(), param("organizationId").exists().trim(),
body('productId').exists().isString(), body("productId").exists().isString(),
validateRequest, validateRequest,
organizationsController.updateOrganizationPlan organizationsController.updateOrganizationPlan
); );
router.get( router.get(
'/:organizationId/billing-details/payment-methods', "/:organizationId/billing-details/payment-methods",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireOrganizationAuth({ requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER], acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED],
}), }),
param('organizationId').exists().trim(), param("organizationId").exists().trim(),
validateRequest, validateRequest,
organizationsController.getOrganizationPmtMethods organizationsController.getOrganizationPmtMethods
); );
router.post( router.post(
'/:organizationId/billing-details/payment-methods', "/:organizationId/billing-details/payment-methods",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireOrganizationAuth({ requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER], acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED],
}), }),
param('organizationId').exists().trim(), param("organizationId").exists().trim(),
body('success_url').exists().isString(), body("success_url").exists().isString(),
body('cancel_url').exists().isString(), body("cancel_url").exists().isString(),
validateRequest, validateRequest,
organizationsController.addOrganizationPmtMethod organizationsController.addOrganizationPmtMethod
); );
router.delete( router.delete(
'/:organizationId/billing-details/payment-methods/:pmtMethodId', "/:organizationId/billing-details/payment-methods/:pmtMethodId",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireOrganizationAuth({ requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER], acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED] acceptedStatuses: [ACCEPTED],
}), }),
param('organizationId').exists().trim(), param("organizationId").exists().trim(),
validateRequest, validateRequest,
organizationsController.deleteOrganizationPmtMethod organizationsController.deleteOrganizationPmtMethod
); );
+18 -18
View File
@@ -1,46 +1,46 @@
import express from 'express'; import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
requireAuth, requireAuth,
requireSecretAuth, requireSecretAuth,
validateRequest validateRequest,
} from '../../../middleware'; } from "../../../middleware";
import { query, param, body } from 'express-validator'; import { body, param, query } from "express-validator";
import { secretController } from '../../controllers/v1'; import { secretController } from "../../controllers/v1";
import { import {
ADMIN, ADMIN,
MEMBER, MEMBER,
PERMISSION_READ_SECRETS, PERMISSION_READ_SECRETS,
PERMISSION_WRITE_SECRETS PERMISSION_WRITE_SECRETS,
} from '../../../variables'; } from "../../../variables";
router.get( router.get(
'/:secretId/secret-versions', "/:secretId/secret-versions",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireSecretAuth({ requireSecretAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
requiredPermissions: [PERMISSION_READ_SECRETS] requiredPermissions: [PERMISSION_READ_SECRETS],
}), }),
param('secretId').exists().trim(), param("secretId").exists().trim(),
query('offset').exists().isInt(), query("offset").exists().isInt(),
query('limit').exists().isInt(), query("limit").exists().isInt(),
validateRequest, validateRequest,
secretController.getSecretVersions secretController.getSecretVersions
); );
router.post( router.post(
'/:secretId/secret-versions/rollback', "/:secretId/secret-versions/rollback",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt', 'apiKey'] acceptedAuthModes: ["jwt", "apiKey"],
}), }),
requireSecretAuth({ requireSecretAuth({
acceptedRoles: [ADMIN, MEMBER], acceptedRoles: [ADMIN, MEMBER],
requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS] requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS],
}), }),
param('secretId').exists().trim(), param("secretId").exists().trim(),
body('version').exists().isInt(), body("version").exists().isInt(),
secretController.rollbackSecretVersion secretController.rollbackSecretVersion
); );
+12 -12
View File
@@ -1,25 +1,25 @@
import express from 'express'; import express from "express";
const router = express.Router(); const router = express.Router();
import { import {
requireSecretSnapshotAuth requireSecretSnapshotAuth,
} from '../../middleware'; } from "../../middleware";
import { import {
requireAuth, requireAuth,
validateRequest validateRequest,
} from '../../../middleware'; } from "../../../middleware";
import { param } from 'express-validator'; import { param } from "express-validator";
import { ADMIN, MEMBER } from '../../../variables'; import { ADMIN, MEMBER } from "../../../variables";
import { secretSnapshotController } from '../../controllers/v1'; import { secretSnapshotController } from "../../controllers/v1";
router.get( router.get(
'/:secretSnapshotId', "/:secretSnapshotId",
requireAuth({ requireAuth({
acceptedAuthModes: ['jwt'] acceptedAuthModes: ["jwt"],
}), }),
requireSecretSnapshotAuth({ requireSecretSnapshotAuth({
acceptedRoles: [ADMIN, MEMBER] acceptedRoles: [ADMIN, MEMBER],
}), }),
param('secretSnapshotId').exists().trim(), param("secretSnapshotId").exists().trim(),
validateRequest, validateRequest,
secretSnapshotController.getSecretSnapshot secretSnapshotController.getSecretSnapshot
); );
+3 -3
View File
@@ -1,7 +1,7 @@
import express from 'express'; import express from "express";
const router = express.Router(); const router = express.Router();
import { stripeController } from '../../controllers/v1'; import { stripeController } from "../../controllers/v1";
router.post('/webhook', stripeController.handleWebhook); router.post("/webhook", stripeController.handleWebhook);
export default router; export default router;
+1 -1
View File
@@ -5,7 +5,7 @@ import {
requireWorkspaceAuth, requireWorkspaceAuth,
validateRequest, validateRequest,
} from "../../../middleware"; } from "../../../middleware";
import { param, query, body } from "express-validator"; import { body, param, query } from "express-validator";
import { ADMIN, MEMBER } from "../../../variables"; import { ADMIN, MEMBER } from "../../../variables";
import { workspaceController } from "../../controllers/v1"; import { workspaceController } from "../../controllers/v1";
+19 -19
View File
@@ -1,22 +1,22 @@
import * as Sentry from '@sentry/node'; import * as Sentry from "@sentry/node";
import NodeCache from 'node-cache'; import NodeCache from "node-cache";
import { import {
getLicenseKey, getLicenseKey,
getLicenseServerKey, getLicenseServerKey,
getLicenseServerUrl getLicenseServerUrl,
} from '../../config'; } from "../../config";
import { import {
licenseKeyRequest, licenseKeyRequest,
licenseServerKeyRequest, licenseServerKeyRequest,
refreshLicenseKeyToken,
refreshLicenseServerKeyToken, refreshLicenseServerKeyToken,
refreshLicenseKeyToken } from "../../config/request";
} from '../../config/request'; import { Organization } from "../../models";
import { Organization } from '../../models'; import { OrganizationNotFoundError } from "../../utils/errors";
import { OrganizationNotFoundError } from '../../utils/errors';
interface FeatureSet { interface FeatureSet {
_id: string | null; _id: string | null;
slug: 'starter' | 'team' | 'pro' | 'enterprise' | null; slug: "starter" | "team" | "pro" | "enterprise" | null;
tier: number; tier: number;
workspaceLimit: number | null; workspaceLimit: number | null;
workspacesUsed: number; workspacesUsed: number;
@@ -42,7 +42,7 @@ class EELicenseService {
private readonly _isLicenseValid: boolean; // TODO: deprecate private readonly _isLicenseValid: boolean; // TODO: deprecate
public instanceType: 'self-hosted' | 'enterprise-self-hosted' | 'cloud' = 'self-hosted'; public instanceType: "self-hosted" | "enterprise-self-hosted" | "cloud" = "self-hosted";
public globalFeatureSet: FeatureSet = { public globalFeatureSet: FeatureSet = {
_id: null, _id: null,
@@ -59,7 +59,7 @@ class EELicenseService {
rbac: true, rbac: true,
customRateLimits: true, customRateLimits: true,
customAlerts: true, customAlerts: true,
auditLogs: false auditLogs: false,
} }
public localFeatureSet: NodeCache; public localFeatureSet: NodeCache;
@@ -67,14 +67,14 @@ class EELicenseService {
constructor() { constructor() {
this._isLicenseValid = true; this._isLicenseValid = true;
this.localFeatureSet = new NodeCache({ this.localFeatureSet = new NodeCache({
stdTTL: 300 stdTTL: 300,
}); });
} }
public async getPlan(organizationId: string, workspaceId?: string): Promise<FeatureSet> { public async getPlan(organizationId: string, workspaceId?: string): Promise<FeatureSet> {
try { try {
if (this.instanceType === 'cloud') { if (this.instanceType === "cloud") {
const cachedPlan = this.localFeatureSet.get<FeatureSet>(`${organizationId}-${workspaceId ?? ''}`); const cachedPlan = this.localFeatureSet.get<FeatureSet>(`${organizationId}-${workspaceId ?? ""}`);
if (cachedPlan) { if (cachedPlan) {
return cachedPlan; return cachedPlan;
} }
@@ -91,7 +91,7 @@ class EELicenseService {
const { data: { currentPlan } } = await licenseServerKeyRequest.get(url); const { data: { currentPlan } } = await licenseServerKeyRequest.get(url);
// cache fetched plan for organization // cache fetched plan for organization
this.localFeatureSet.set(`${organizationId}-${workspaceId ?? ''}`, currentPlan); this.localFeatureSet.set(`${organizationId}-${workspaceId ?? ""}`, currentPlan);
return currentPlan; return currentPlan;
} }
@@ -103,8 +103,8 @@ class EELicenseService {
} }
public async refreshPlan(organizationId: string, workspaceId?: string) { public async refreshPlan(organizationId: string, workspaceId?: string) {
if (this.instanceType === 'cloud') { if (this.instanceType === "cloud") {
this.localFeatureSet.del(`${organizationId}-${workspaceId ?? ''}`); this.localFeatureSet.del(`${organizationId}-${workspaceId ?? ""}`);
await this.getPlan(organizationId, workspaceId); await this.getPlan(organizationId, workspaceId);
} }
} }
@@ -119,7 +119,7 @@ class EELicenseService {
const token = await refreshLicenseServerKeyToken() const token = await refreshLicenseServerKeyToken()
if (token) { if (token) {
this.instanceType = 'cloud'; this.instanceType = "cloud";
} }
return; return;
@@ -135,7 +135,7 @@ class EELicenseService {
); );
this.globalFeatureSet = currentPlan; this.globalFeatureSet = currentPlan;
this.instanceType = 'enterprise-self-hosted'; this.instanceType = "enterprise-self-hosted";
} }
} }
} catch (err) { } catch (err) {
+12 -12
View File
@@ -1,14 +1,14 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import {
IAction IAction,
} from '../models'; } from "../models";
import { import {
createLogHelper createLogHelper,
} from '../helpers/log'; } from "../helpers/log";
import { import {
createActionHelper createActionHelper,
} from '../helpers/action'; } from "../helpers/action";
import EELicenseService from './EELicenseService'; import EELicenseService from "./EELicenseService";
/** /**
* Class to handle Enterprise Edition log actions * Class to handle Enterprise Edition log actions
@@ -31,7 +31,7 @@ class EELogService {
workspaceId, workspaceId,
actions, actions,
channel, channel,
ipAddress ipAddress,
}: { }: {
userId?: Types.ObjectId; userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId; serviceAccountId?: Types.ObjectId;
@@ -49,7 +49,7 @@ class EELogService {
workspaceId, workspaceId,
actions, actions,
channel, channel,
ipAddress ipAddress,
}) })
} }
@@ -68,7 +68,7 @@ class EELogService {
serviceAccountId, serviceAccountId,
serviceTokenDataId, serviceTokenDataId,
workspaceId, workspaceId,
secretIds secretIds,
}: { }: {
name: string; name: string;
userId?: Types.ObjectId; userId?: Types.ObjectId;
@@ -83,7 +83,7 @@ class EELogService {
serviceAccountId, serviceAccountId,
serviceTokenDataId, serviceTokenDataId,
workspaceId, workspaceId,
secretIds secretIds,
}); });
} }
} }
+5 -5
View File
@@ -1,11 +1,11 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { ISecretVersion } from '../models'; import { ISecretVersion } from "../models";
import { import {
takeSecretSnapshotHelper,
addSecretVersionsHelper, addSecretVersionsHelper,
markDeletedSecretVersionsHelper, markDeletedSecretVersionsHelper,
} from '../helpers/secret'; takeSecretSnapshotHelper,
import EELicenseService from './EELicenseService'; } from "../helpers/secret";
import EELicenseService from "./EELicenseService";
/** /**
* Class to handle Enterprise Edition secret actions * Class to handle Enterprise Edition secret actions
+1 -1
View File
@@ -5,5 +5,5 @@ import EELogService from "./EELogService";
export { export {
EELicenseService, EELicenseService,
EESecretService, EESecretService,
EELogService EELogService,
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { eventPushSecrets } from "./secret" import { eventPushSecrets } from "./secret"
export { export {
eventPushSecrets eventPushSecrets,
} }
+8 -8
View File
@@ -1,8 +1,8 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import {
EVENT_PULL_SECRETS,
EVENT_PUSH_SECRETS, EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS } from "../variables";
} from '../variables';
interface PushSecret { interface PushSecret {
ciphertextKey: string; ciphertextKey: string;
@@ -13,7 +13,7 @@ interface PushSecret {
ivValue: string; ivValue: string;
tagValue: string; tagValue: string;
hashValue: string; hashValue: string;
type: 'shared' | 'personal'; type: "shared" | "personal";
} }
/** /**
@@ -24,7 +24,7 @@ interface PushSecret {
*/ */
const eventPushSecrets = ({ const eventPushSecrets = ({
workspaceId, workspaceId,
environment environment,
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment?: string; environment?: string;
@@ -35,7 +35,7 @@ const eventPushSecrets = ({
environment, environment,
payload: { payload: {
} },
}); });
} }
@@ -55,10 +55,10 @@ const eventPullSecrets = ({
workspaceId, workspaceId,
payload: { payload: {
} },
}); });
} }
export { export {
eventPushSecrets eventPushSecrets,
} }
+89 -89
View File
@@ -1,36 +1,36 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import jwt from 'jsonwebtoken'; import jwt from "jsonwebtoken";
import bcrypt from 'bcrypt'; import bcrypt from "bcrypt";
import { import {
IUser,
User,
ServiceTokenData,
ServiceAccount,
APIKeyData, APIKeyData,
ITokenVersion,
IUser,
ServiceAccount,
ServiceTokenData,
TokenVersion, TokenVersion,
ITokenVersion User,
} from '../models'; } from "../models";
import { import {
AccountNotFoundError,
ServiceTokenDataNotFoundError,
ServiceAccountNotFoundError,
APIKeyDataNotFoundError, APIKeyDataNotFoundError,
AccountNotFoundError,
BadRequestError,
ServiceAccountNotFoundError,
ServiceTokenDataNotFoundError,
UnauthorizedRequestError, UnauthorizedRequestError,
BadRequestError } from "../utils/errors";
} from '../utils/errors';
import { import {
getJwtAuthLifetime, getJwtAuthLifetime,
getJwtAuthSecret, getJwtAuthSecret,
getJwtProviderAuthSecret, getJwtProviderAuthSecret,
getJwtRefreshLifetime, getJwtRefreshLifetime,
getJwtRefreshSecret getJwtRefreshSecret,
} from '../config'; } from "../config";
import { import {
AUTH_MODE_API_KEY,
AUTH_MODE_JWT, AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY } from "../variables";
} from '../variables';
/** /**
* *
@@ -39,41 +39,41 @@ import {
*/ */
export const validateAuthMode = ({ export const validateAuthMode = ({
headers, headers,
acceptedAuthModes acceptedAuthModes,
}: { }: {
headers: { [key: string]: string | string[] | undefined }, headers: { [key: string]: string | string[] | undefined },
acceptedAuthModes: string[] acceptedAuthModes: string[]
}) => { }) => {
const apiKey = headers['x-api-key']; const apiKey = headers["x-api-key"];
const authHeader = headers['authorization']; const authHeader = headers["authorization"];
let authMode, authTokenValue; let authMode, authTokenValue;
if (apiKey === undefined && authHeader === undefined) { if (apiKey === undefined && authHeader === undefined) {
// case: no auth or X-API-KEY header present // case: no auth or X-API-KEY header present
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' }); throw BadRequestError({ message: "Missing Authorization or X-API-KEY in request header." });
} }
if (typeof apiKey === 'string') { if (typeof apiKey === "string") {
// case: treat request authentication type as via X-API-KEY (i.e. API Key) // case: treat request authentication type as via X-API-KEY (i.e. API Key)
authMode = AUTH_MODE_API_KEY; authMode = AUTH_MODE_API_KEY;
authTokenValue = apiKey; authTokenValue = apiKey;
} }
if (typeof authHeader === 'string') { if (typeof authHeader === "string") {
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token) // case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
const [tokenType, tokenValue] = <[string, string]>authHeader.split(' ', 2) ?? [null, null] const [tokenType, tokenValue] = <[string, string]>authHeader.split(" ", 2) ?? [null, null]
if (tokenType === null) if (tokenType === null)
throw BadRequestError({ message: `Missing Authorization Header in the request header.` }); throw BadRequestError({ message: "Missing Authorization Header in the request header." });
if (tokenType.toLowerCase() !== 'bearer') if (tokenType.toLowerCase() !== "bearer")
throw BadRequestError({ message: `The provided authentication type '${tokenType}' is not supported.` }); throw BadRequestError({ message: `The provided authentication type '${tokenType}' is not supported.` });
if (tokenValue === null) if (tokenValue === null)
throw BadRequestError({ message: 'Missing Authorization Body in the request header.' }); throw BadRequestError({ message: "Missing Authorization Body in the request header." });
switch (tokenValue.split('.', 1)[0]) { switch (tokenValue.split(".", 1)[0]) {
case 'st': case "st":
authMode = AUTH_MODE_SERVICE_TOKEN; authMode = AUTH_MODE_SERVICE_TOKEN;
break; break;
case 'sa': case "sa":
authMode = AUTH_MODE_SERVICE_ACCOUNT; authMode = AUTH_MODE_SERVICE_ACCOUNT;
break; break;
default: default:
@@ -83,13 +83,13 @@ export const validateAuthMode = ({
authTokenValue = tokenValue; authTokenValue = tokenValue;
} }
if (!authMode || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' }); if (!authMode || !authTokenValue) throw BadRequestError({ message: "Missing valid Authorization or X-API-KEY in request header." });
if (!acceptedAuthModes.includes(authMode)) throw BadRequestError({ message: 'The provided authentication type is not supported.' }); if (!acceptedAuthModes.includes(authMode)) throw BadRequestError({ message: "The provided authentication type is not supported." });
return ({ return ({
authMode, authMode,
authTokenValue authTokenValue,
}); });
} }
@@ -100,7 +100,7 @@ export const validateAuthMode = ({
* @returns {User} user - user corresponding to JWT token * @returns {User} user - user corresponding to JWT token
*/ */
export const getAuthUserPayload = async ({ export const getAuthUserPayload = async ({
authTokenValue authTokenValue,
}: { }: {
authTokenValue: string; authTokenValue: string;
}) => { }) => {
@@ -109,31 +109,31 @@ export const getAuthUserPayload = async ({
); );
const user = await User.findOne({ const user = await User.findOne({
_id: new Types.ObjectId(decodedToken.userId) _id: new Types.ObjectId(decodedToken.userId),
}).select('+publicKey +accessVersion'); }).select("+publicKey +accessVersion");
if (!user) throw AccountNotFoundError({ message: 'Failed to find user' }); if (!user) throw AccountNotFoundError({ message: "Failed to find user" });
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate user with partially set up account' }); if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
const tokenVersion = await TokenVersion.findOneAndUpdate({ const tokenVersion = await TokenVersion.findOneAndUpdate({
_id: new Types.ObjectId(decodedToken.tokenVersionId), _id: new Types.ObjectId(decodedToken.tokenVersionId),
user: user._id user: user._id,
}, { }, {
lastUsed: new Date() lastUsed: new Date(),
}); });
if (!tokenVersion) throw UnauthorizedRequestError({ if (!tokenVersion) throw UnauthorizedRequestError({
message: 'Failed to validate access token' message: "Failed to validate access token",
}); });
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({ if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
message: 'Failed to validate access token' message: "Failed to validate access token",
}); });
return ({ return ({
user, user,
tokenVersionId: tokenVersion._id tokenVersionId: tokenVersion._id,
}); });
} }
@@ -144,41 +144,41 @@ export const getAuthUserPayload = async ({
* @returns {ServiceTokenData} serviceTokenData - service token data * @returns {ServiceTokenData} serviceTokenData - service token data
*/ */
export const getAuthSTDPayload = async ({ export const getAuthSTDPayload = async ({
authTokenValue authTokenValue,
}: { }: {
authTokenValue: string; authTokenValue: string;
}) => { }) => {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3); const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
let serviceTokenData = await ServiceTokenData let serviceTokenData = await ServiceTokenData
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt'); .findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt");
if (!serviceTokenData) { if (!serviceTokenData) {
throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' }); throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) { } else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
// case: service token expired // case: service token expired
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id); await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'Failed to authenticate expired service token' message: "Failed to authenticate expired service token",
}); });
} }
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash); const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({ if (!isMatch) throw UnauthorizedRequestError({
message: 'Failed to authenticate service token' message: "Failed to authenticate service token",
}); });
serviceTokenData = await ServiceTokenData serviceTokenData = await ServiceTokenData
.findOneAndUpdate({ .findOneAndUpdate({
_id: new Types.ObjectId(TOKEN_IDENTIFIER) _id: new Types.ObjectId(TOKEN_IDENTIFIER),
}, { }, {
lastUsed: new Date() lastUsed: new Date(),
}, { }, {
new: true new: true,
}) })
.select('+encryptedKey +iv +tag'); .select("+encryptedKey +iv +tag");
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' }); if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
return serviceTokenData; return serviceTokenData;
} }
@@ -190,23 +190,23 @@ export const getAuthSTDPayload = async ({
* @returns {ServiceAccount} serviceAccount * @returns {ServiceAccount} serviceAccount
*/ */
export const getAuthSAAKPayload = async ({ export const getAuthSAAKPayload = async ({
authTokenValue authTokenValue,
}: { }: {
authTokenValue: string; authTokenValue: string;
}) => { }) => {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3); const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
const serviceAccount = await ServiceAccount.findById( const serviceAccount = await ServiceAccount.findById(
Buffer.from(TOKEN_IDENTIFIER, 'base64').toString('hex') Buffer.from(TOKEN_IDENTIFIER, "base64").toString("hex")
).select('+secretHash'); ).select("+secretHash");
if (!serviceAccount) { if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' }); throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
} }
const result = await bcrypt.compare(TOKEN_SECRET, serviceAccount.secretHash); const result = await bcrypt.compare(TOKEN_SECRET, serviceAccount.secretHash);
if (!result) throw UnauthorizedRequestError({ if (!result) throw UnauthorizedRequestError({
message: 'Failed to authenticate service account access key' message: "Failed to authenticate service account access key",
}); });
return serviceAccount; return serviceAccount;
@@ -219,48 +219,48 @@ export const getAuthSAAKPayload = async ({
* @returns {APIKeyData} apiKeyData - API key data * @returns {APIKeyData} apiKeyData - API key data
*/ */
export const getAuthAPIKeyPayload = async ({ export const getAuthAPIKeyPayload = async ({
authTokenValue authTokenValue,
}: { }: {
authTokenValue: string; authTokenValue: string;
}) => { }) => {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3); const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
let apiKeyData = await APIKeyData let apiKeyData = await APIKeyData
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt') .findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
.populate<{ user: IUser }>('user', '+publicKey'); .populate<{ user: IUser }>("user", "+publicKey");
if (!apiKeyData) { if (!apiKeyData) {
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' }); throw APIKeyDataNotFoundError({ message: "Failed to find API key data" });
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) { } else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
// case: API key expired // case: API key expired
await APIKeyData.findByIdAndDelete(apiKeyData._id); await APIKeyData.findByIdAndDelete(apiKeyData._id);
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
message: 'Failed to authenticate expired API key' message: "Failed to authenticate expired API key",
}); });
} }
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash); const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({ if (!isMatch) throw UnauthorizedRequestError({
message: 'Failed to authenticate API key' message: "Failed to authenticate API key",
}); });
apiKeyData = await APIKeyData.findOneAndUpdate({ apiKeyData = await APIKeyData.findOneAndUpdate({
_id: new Types.ObjectId(TOKEN_IDENTIFIER) _id: new Types.ObjectId(TOKEN_IDENTIFIER),
}, { }, {
lastUsed: new Date() lastUsed: new Date(),
}, { }, {
new: true new: true,
}); });
if (!apiKeyData) { if (!apiKeyData) {
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' }); throw APIKeyDataNotFoundError({ message: "Failed to find API key data" });
} }
const user = await User.findById(apiKeyData.user).select('+publicKey'); const user = await User.findById(apiKeyData.user).select("+publicKey");
if (!user) { if (!user) {
throw AccountNotFoundError({ throw AccountNotFoundError({
message: 'Failed to find user' message: "Failed to find user",
}); });
} }
@@ -278,7 +278,7 @@ export const getAuthAPIKeyPayload = async ({
export const issueAuthTokens = async ({ export const issueAuthTokens = async ({
userId, userId,
ip, ip,
userAgent userAgent,
}: { }: {
userId: Types.ObjectId; userId: Types.ObjectId;
ip: string; ip: string;
@@ -290,7 +290,7 @@ export const issueAuthTokens = async ({
tokenVersion = await TokenVersion.findOne({ tokenVersion = await TokenVersion.findOne({
user: userId, user: userId,
ip, ip,
userAgent userAgent,
}); });
if (!tokenVersion) { if (!tokenVersion) {
@@ -302,7 +302,7 @@ export const issueAuthTokens = async ({
accessVersion: 0, accessVersion: 0,
ip, ip,
userAgent, userAgent,
lastUsed: new Date() lastUsed: new Date(),
}).save(); }).save();
} }
@@ -311,25 +311,25 @@ export const issueAuthTokens = async ({
payload: { payload: {
userId, userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.accessVersion accessVersion: tokenVersion.accessVersion,
}, },
expiresIn: await getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret() secret: await getJwtAuthSecret(),
}); });
const refreshToken = createToken({ const refreshToken = createToken({
payload: { payload: {
userId, userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
refreshVersion: tokenVersion.refreshVersion refreshVersion: tokenVersion.refreshVersion,
}, },
expiresIn: await getJwtRefreshLifetime(), expiresIn: await getJwtRefreshLifetime(),
secret: await getJwtRefreshSecret() secret: await getJwtRefreshSecret(),
}); });
return { return {
token, token,
refreshToken refreshToken,
}; };
}; };
@@ -342,12 +342,12 @@ export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void>
// increment refreshVersion on user by 1 // increment refreshVersion on user by 1
await TokenVersion.findOneAndUpdate({ await TokenVersion.findOneAndUpdate({
_id: tokenVersionId _id: tokenVersionId,
}, { }, {
$inc: { $inc: {
refreshVersion: 1, refreshVersion: 1,
accessVersion: 1 accessVersion: 1,
} },
}); });
}; };
@@ -362,14 +362,14 @@ export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void>
export const createToken = ({ export const createToken = ({
payload, payload,
expiresIn, expiresIn,
secret secret,
}: { }: {
payload: any; payload: any;
expiresIn: string | number; expiresIn: string | number;
secret: string; secret: string;
}) => { }) => {
return jwt.sign(payload, secret, { return jwt.sign(payload, secret, {
expiresIn expiresIn,
}); });
}; };
@@ -383,7 +383,7 @@ export const validateProviderAuthToken = async ({
providerAuthToken?: string; providerAuthToken?: string;
}) => { }) => {
if (!providerAuthToken) { if (!providerAuthToken) {
throw new Error('Invalid authentication request.'); throw new Error("Invalid authentication request.");
} }
const decodedToken = <jwt.ProviderAuthJwtPayload>( const decodedToken = <jwt.ProviderAuthJwtPayload>(
@@ -394,6 +394,6 @@ export const validateProviderAuthToken = async ({
decodedToken.authProvider !== user.authProvider || decodedToken.authProvider !== user.authProvider ||
decodedToken.email !== email decodedToken.email !== email
) { ) {
throw new Error('Invalid authentication credentials.') throw new Error("Invalid authentication credentials.")
} }
} }
+7 -7
View File
@@ -1,18 +1,18 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Bot, BotKey, Secret, ISecret, IUser } from "../models"; import { Bot, BotKey, ISecret, IUser, Secret } from "../models";
import { import {
generateKeyPair,
encryptSymmetric128BitHexKeyUTF8,
decryptSymmetric128BitHexKeyUTF8,
decryptAsymmetric, decryptAsymmetric,
decryptSymmetric128BitHexKeyUTF8,
encryptSymmetric128BitHexKeyUTF8,
generateKeyPair,
} from "../utils/crypto"; } from "../utils/crypto";
import { import {
SECRET_SHARED,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
SECRET_SHARED,
} from "../variables"; } from "../variables";
import { getEncryptionKey, getRootEncryptionKey, client } from "../config"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { InternalServerError } from "../utils/errors"; import { InternalServerError } from "../utils/errors";
import Folder from "../models/folder"; import Folder from "../models/folder";
import { getFolderByPath } from "../services/FolderService"; import { getFolderByPath } from "../services/FolderService";
+7 -7
View File
@@ -1,5 +1,5 @@
import mongoose from 'mongoose'; import mongoose from "mongoose";
import { getLogger } from '../utils/logger'; import { getLogger } from "../utils/logger";
/** /**
* Initialize database connection * Initialize database connection
@@ -8,7 +8,7 @@ import { getLogger } from '../utils/logger';
* @returns * @returns
*/ */
export const initDatabaseHelper = async ({ export const initDatabaseHelper = async ({
mongoURL mongoURL,
}: { }: {
mongoURL: string; mongoURL: string;
}) => { }) => {
@@ -16,7 +16,7 @@ export const initDatabaseHelper = async ({
await mongoose.connect(mongoURL); await mongoose.connect(mongoURL);
// allow empty strings to pass the required validator // allow empty strings to pass the required validator
mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string'); mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
(await getLogger("database")).info("Database connection established"); (await getLogger("database")).info("Database connection established");
@@ -35,10 +35,10 @@ export const closeDatabaseHelper = async () => {
new Promise((resolve) => { new Promise((resolve) => {
if (mongoose.connection && mongoose.connection.readyState == 1) { if (mongoose.connection && mongoose.connection.readyState == 1) {
mongoose.connection.close() mongoose.connection.close()
.then(() => resolve('Database connection closed')); .then(() => resolve("Database connection closed"));
} else { } else {
resolve('Database connection already closed'); resolve("Database connection already closed");
} }
}) }),
]); ]);
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Bot, IBot } from "../models"; import { Bot } from "../models";
import { EVENT_PUSH_SECRETS } from "../variables"; import { EVENT_PUSH_SECRETS } from "../variables";
import { IntegrationService } from "../services"; import { IntegrationService } from "../services";
+17 -17
View File
@@ -1,17 +1,17 @@
export * from './auth'; export * from "./auth";
export * from './bot'; export * from "./bot";
export * from './database'; export * from "./database";
export * from './event'; export * from "./event";
export * from './integration'; export * from "./integration";
export * from './key'; export * from "./key";
export * from './membership'; export * from "./membership";
export * from './membershipOrg'; export * from "./membershipOrg";
export * from './nodemailer'; export * from "./nodemailer";
export * from './organization'; export * from "./organization";
export * from './rateLimiter'; export * from "./rateLimiter";
export * from './secret'; export * from "./secret";
export * from './secrets'; export * from "./secrets";
export * from './signup'; export * from "./signup";
export * from './token'; export * from "./token";
export * from './user'; export * from "./user";
export * from './workspace'; export * from "./workspace";
+2 -2
View File
@@ -3,10 +3,10 @@ import { Bot, Integration, IntegrationAuth } from "../models";
import { exchangeCode, exchangeRefresh, syncSecrets } from "../integrations"; import { exchangeCode, exchangeRefresh, syncSecrets } from "../integrations";
import { BotService } from "../services"; import { BotService } from "../services";
import { import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
INTEGRATION_NETLIFY,
INTEGRATION_VERCEL,
} from "../variables"; } from "../variables";
import { UnauthorizedRequestError } from "../utils/errors"; import { UnauthorizedRequestError } from "../utils/errors";
+5 -5
View File
@@ -1,4 +1,4 @@
import { Key, IKey } from '../models'; import { IKey, Key } from "../models";
interface Key { interface Key {
encryptedKey: string; encryptedKey: string;
@@ -20,7 +20,7 @@ interface Key {
export const pushKeys = async ({ export const pushKeys = async ({
userId, userId,
workspaceId, workspaceId,
keys keys,
}: { }: {
userId: string; userId: string;
workspaceId: string; workspaceId: string;
@@ -31,9 +31,9 @@ export const pushKeys = async ({
( (
await Key.find( await Key.find(
{ {
workspace: workspaceId workspace: workspaceId,
}, },
'receiver' "receiver"
) )
).map((k: IKey) => k.receiver.toString()) ).map((k: IKey) => k.receiver.toString())
); );
@@ -47,7 +47,7 @@ export const pushKeys = async ({
nonce: k.nonce, nonce: k.nonce,
sender: userId, sender: userId,
receiver: k.userId, receiver: k.userId,
workspace: workspaceId workspace: workspaceId,
})) }))
); );
}; };
+4 -4
View File
@@ -1,6 +1,6 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Membership, Key } from "../models"; import { Key, Membership } from "../models";
import { MembershipNotFoundError, BadRequestError } from "../utils/errors"; import { BadRequestError, MembershipNotFoundError } from "../utils/errors";
/** /**
* Validate that user with id [userId] is a member of workspace with id [workspaceId] * Validate that user with id [userId] is a member of workspace with id [workspaceId]
@@ -62,7 +62,7 @@ export const findMembership = async (queryObj: any) => {
export const addMemberships = async ({ export const addMemberships = async ({
userIds, userIds,
workspaceId, workspaceId,
roles roles,
}: { }: {
userIds: string[]; userIds: string[];
workspaceId: string; workspaceId: string;
@@ -95,7 +95,7 @@ export const addMemberships = async ({
*/ */
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => { export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
const deletedMembership = await Membership.findOneAndDelete({ const deletedMembership = await Membership.findOneAndDelete({
_id: membershipId _id: membershipId,
}); });
// delete keys associated with the membership // delete keys associated with the membership
+26 -26
View File
@@ -1,14 +1,14 @@
import { Types } from 'mongoose'; import { Types } from "mongoose";
import { import {
Key,
Membership,
MembershipOrg, MembershipOrg,
Workspace, Workspace,
Membership, } from "../models";
Key
} from '../models';
import { import {
MembershipOrgNotFoundError, MembershipOrgNotFoundError,
UnauthorizedRequestError UnauthorizedRequestError,
} from '../utils/errors'; } from "../utils/errors";
/** /**
* Validate that user with id [userId] is a member of organization with id [organizationId] * Validate that user with id [userId] is a member of organization with id [organizationId]
@@ -22,31 +22,31 @@ export const validateMembershipOrg = async ({
userId, userId,
organizationId, organizationId,
acceptedRoles, acceptedRoles,
acceptedStatuses acceptedStatuses,
}: { }: {
userId: Types.ObjectId; userId: Types.ObjectId;
organizationId: Types.ObjectId; organizationId: Types.ObjectId;
acceptedRoles?: Array<'owner' | 'admin' | 'member'>; acceptedRoles?: Array<"owner" | "admin" | "member">;
acceptedStatuses?: Array<'invited' | 'accepted'>; acceptedStatuses?: Array<"invited" | "accepted">;
}) => { }) => {
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
user: userId, user: userId,
organization: organizationId organization: organizationId,
}); });
if (!membershipOrg) { if (!membershipOrg) {
throw MembershipOrgNotFoundError({ message: 'Failed to find organization membership' }); throw MembershipOrgNotFoundError({ message: "Failed to find organization membership" });
} }
if (acceptedRoles) { if (acceptedRoles) {
if (!acceptedRoles.includes(membershipOrg.role)) { if (!acceptedRoles.includes(membershipOrg.role)) {
throw UnauthorizedRequestError({ message: 'Failed to validate organization membership role' }); throw UnauthorizedRequestError({ message: "Failed to validate organization membership role" });
} }
} }
if (acceptedStatuses) { if (acceptedStatuses) {
if (!acceptedStatuses.includes(membershipOrg.status)) { if (!acceptedStatuses.includes(membershipOrg.status)) {
throw UnauthorizedRequestError({ message: 'Failed to validate organization membership status' }); throw UnauthorizedRequestError({ message: "Failed to validate organization membership status" });
} }
} }
@@ -76,7 +76,7 @@ export const addMembershipsOrg = async ({
userIds, userIds,
organizationId, organizationId,
roles, roles,
statuses statuses,
}: { }: {
userIds: string[]; userIds: string[];
organizationId: string; organizationId: string;
@@ -90,16 +90,16 @@ export const addMembershipsOrg = async ({
user: userId, user: userId,
organization: organizationId, organization: organizationId,
role: roles[idx], role: roles[idx],
status: statuses[idx] status: statuses[idx],
}, },
update: { update: {
user: userId, user: userId,
organization: organizationId, organization: organizationId,
role: roles[idx], role: roles[idx],
status: statuses[idx] status: statuses[idx],
}, },
upsert: true upsert: true,
} },
}; };
}); });
@@ -112,15 +112,15 @@ export const addMembershipsOrg = async ({
* @param {String} obj.membershipOrgId - id of organization membership to delete * @param {String} obj.membershipOrgId - id of organization membership to delete
*/ */
export const deleteMembershipOrg = async ({ export const deleteMembershipOrg = async ({
membershipOrgId membershipOrgId,
}: { }: {
membershipOrgId: string; membershipOrgId: string;
}) => { }) => {
const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({ const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
_id: membershipOrgId _id: membershipOrgId,
}); });
if (!deletedMembershipOrg) throw new Error('Failed to delete organization membership'); if (!deletedMembershipOrg) throw new Error("Failed to delete organization membership");
// delete keys associated with organization membership // delete keys associated with organization membership
if (deletedMembershipOrg?.user) { if (deletedMembershipOrg?.user) {
@@ -128,22 +128,22 @@ export const deleteMembershipOrg = async ({
const workspaces = ( const workspaces = (
await Workspace.find({ await Workspace.find({
organization: deletedMembershipOrg.organization organization: deletedMembershipOrg.organization,
}) })
).map((w) => w._id.toString()); ).map((w) => w._id.toString());
await Membership.deleteMany({ await Membership.deleteMany({
user: deletedMembershipOrg.user, user: deletedMembershipOrg.user,
workspace: { workspace: {
$in: workspaces $in: workspaces,
} },
}); });
await Key.deleteMany({ await Key.deleteMany({
receiver: deletedMembershipOrg.user, receiver: deletedMembershipOrg.user,
workspace: { workspace: {
$in: workspaces $in: workspaces,
} },
}); });
} }
+10 -10
View File
@@ -1,8 +1,8 @@
import fs from 'fs'; import fs from "fs";
import path from 'path'; import path from "path";
import handlebars from 'handlebars'; import handlebars from "handlebars";
import nodemailer from 'nodemailer'; import nodemailer from "nodemailer";
import { getSmtpFromName, getSmtpFromAddress, getSmtpConfigured } from '../config'; import { getSmtpConfigured, getSmtpFromAddress, getSmtpFromName } from "../config";
let smtpTransporter: nodemailer.Transporter; let smtpTransporter: nodemailer.Transporter;
@@ -17,7 +17,7 @@ export const sendMail = async ({
template, template,
subjectLine, subjectLine,
recipients, recipients,
substitutions substitutions,
}: { }: {
template: string; template: string;
subjectLine: string; subjectLine: string;
@@ -26,17 +26,17 @@ export const sendMail = async ({
}) => { }) => {
if (await getSmtpConfigured()) { if (await getSmtpConfigured()) {
const html = fs.readFileSync( const html = fs.readFileSync(
path.resolve(__dirname, '../templates/' + template), path.resolve(__dirname, "../templates/" + template),
'utf8' "utf8"
); );
const temp = handlebars.compile(html); const temp = handlebars.compile(html);
const htmlToSend = temp(substitutions); const htmlToSend = temp(substitutions);
await smtpTransporter.sendMail({ await smtpTransporter.sendMail({
from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`, from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`,
to: recipients.join(', '), to: recipients.join(", "),
subject: subjectLine, subject: subjectLine,
html: htmlToSend html: htmlToSend,
}); });
} }
}; };
+15 -15
View File
@@ -1,25 +1,25 @@
import Stripe from "stripe"; import Stripe from "stripe";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Organization, MembershipOrg } from "../models"; import { MembershipOrg, Organization } from "../models";
import { import {
ACCEPTED ACCEPTED,
} from "../variables"; } from "../variables";
import { import {
getStripeSecretKey,
getStripeProductPro, getStripeProductPro,
getStripeProductTeam,
getStripeProductStarter, getStripeProductStarter,
getStripeProductTeam,
getStripeSecretKey,
} from "../config"; } from "../config";
import { import {
EELicenseService EELicenseService,
} from '../ee/services'; } from "../ee/services";
import { import {
getLicenseServerUrl getLicenseServerUrl,
} from '../config'; } from "../config";
import { import {
licenseKeyRequest,
licenseServerKeyRequest, licenseServerKeyRequest,
licenseKeyRequest } from "../config/request";
} from '../config/request';
/** /**
* Create an organization with name [name] * Create an organization with name [name]
@@ -137,7 +137,7 @@ export const updateSubscriptionOrgQuantity = async ({
}); });
if (organization && organization.customerId) { if (organization && organization.customerId) {
if (EELicenseService.instanceType === 'cloud') { if (EELicenseService.instanceType === "cloud") {
// instance of Infisical is a cloud instance // instance of Infisical is a cloud instance
const quantity = await MembershipOrg.countDocuments({ const quantity = await MembershipOrg.countDocuments({
organization: new Types.ObjectId(organizationId), organization: new Types.ObjectId(organizationId),
@@ -147,7 +147,7 @@ export const updateSubscriptionOrgQuantity = async ({
await licenseServerKeyRequest.patch( await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`, `${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`,
{ {
quantity quantity,
} }
); );
@@ -155,17 +155,17 @@ export const updateSubscriptionOrgQuantity = async ({
} }
} }
if (EELicenseService.instanceType === 'enterprise-self-hosted') { if (EELicenseService.instanceType === "enterprise-self-hosted") {
// instance of Infisical is an enterprise self-hosted instance // instance of Infisical is an enterprise self-hosted instance
const usedSeats = await MembershipOrg.countDocuments({ const usedSeats = await MembershipOrg.countDocuments({
status: ACCEPTED status: ACCEPTED,
}); });
await licenseKeyRequest.patch( await licenseKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license/v1/license`, `${await getLicenseServerUrl()}/api/license/v1/license`,
{ {
usedSeats usedSeats,
} }
); );
} }
+6 -6
View File
@@ -1,4 +1,4 @@
import rateLimit from 'express-rate-limit'; import rateLimit from "express-rate-limit";
// const MongoStore = require('rate-limit-mongo'); // const MongoStore = require('rate-limit-mongo');
// 200 per minute // 200 per minute
@@ -14,11 +14,11 @@ export const apiLimiter = rateLimit({
standardHeaders: true, standardHeaders: true,
legacyHeaders: false, legacyHeaders: false,
skip: (request) => { skip: (request) => {
return request.path === '/healthcheck' || request.path === '/api/status' return request.path === "/healthcheck" || request.path === "/api/status"
}, },
keyGenerator: (req, res) => { keyGenerator: (req, res) => {
return req.realIP return req.realIP
} },
}); });
// 50 requests per 1 hours // 50 requests per 1 hours
@@ -35,7 +35,7 @@ const authLimit = rateLimit({
legacyHeaders: false, legacyHeaders: false,
keyGenerator: (req, res) => { keyGenerator: (req, res) => {
return req.realIP return req.realIP
} },
}); });
// 5 requests per 1 hour // 5 requests per 1 hour
@@ -52,11 +52,11 @@ export const passwordLimiter = rateLimit({
legacyHeaders: false, legacyHeaders: false,
keyGenerator: (req, res) => { keyGenerator: (req, res) => {
return req.realIP return req.realIP
} },
}); });
export const authLimiter = (req: any, res: any, next: any) => { export const authLimiter = (req: any, res: any, next: any) => {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === "production") {
authLimit(req, res, next); authLimit(req, res, next);
} else { } else {
next(); next();
+5 -5
View File
@@ -1,16 +1,16 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Secret, ISecret } from "../models"; import { ISecret, Secret } from "../models";
import { EESecretService, EELogService } from "../ee/services"; import { EELogService, EESecretService } from "../ee/services";
import { IAction, SecretVersion } from "../ee/models"; import { IAction, SecretVersion } from "../ee/models";
import { import {
SECRET_SHARED,
SECRET_PERSONAL,
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS, ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED,
} from "../variables"; } from "../variables";
interface V1PushSecret { interface V1PushSecret {
+23 -23
View File
@@ -1,45 +1,45 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import {
CreateSecretParams, CreateSecretParams,
GetSecretsParams,
GetSecretParams,
UpdateSecretParams,
DeleteSecretParams, DeleteSecretParams,
} from '../interfaces/services/SecretService'; GetSecretParams,
GetSecretsParams,
UpdateSecretParams,
} from "../interfaces/services/SecretService";
import { import {
Secret,
ISecret, ISecret,
Secret,
SecretBlindIndexData, SecretBlindIndexData,
ServiceTokenData, ServiceTokenData,
} from "../models"; } from "../models";
import { SecretVersion } from "../ee/models"; import { SecretVersion } from "../ee/models";
import { import {
BadRequestError, BadRequestError,
SecretNotFoundError,
SecretBlindIndexDataNotFoundError,
InternalServerError, InternalServerError,
SecretBlindIndexDataNotFoundError,
SecretNotFoundError,
UnauthorizedRequestError, UnauthorizedRequestError,
} from "../utils/errors"; } from "../utils/errors";
import { import {
SECRET_PERSONAL,
SECRET_SHARED,
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED,
} from "../variables"; } from "../variables";
import crypto from "crypto"; import crypto from "crypto";
import * as argon2 from "argon2"; import * as argon2 from "argon2";
import { import {
encryptSymmetric128BitHexKeyUTF8,
decryptSymmetric128BitHexKeyUTF8, decryptSymmetric128BitHexKeyUTF8,
} from '../utils/crypto'; encryptSymmetric128BitHexKeyUTF8,
import { TelemetryService } from '../services'; } from "../utils/crypto";
import { getEncryptionKey, client, getRootEncryptionKey } from "../config"; import { TelemetryService } from "../services";
import { EESecretService, EELogService } from "../ee/services"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { EELogService, EESecretService } from "../ee/services";
import { import {
getAuthDataPayloadIdObj, getAuthDataPayloadIdObj,
getAuthDataPayloadUserObj, getAuthDataPayloadUserObj,
@@ -56,7 +56,7 @@ import { getFolderIdFromServiceToken } from "../services/FolderService";
*/ */
export const repackageSecretToRaw = ({ export const repackageSecretToRaw = ({
secret, secret,
key key,
}: { }: {
secret: ISecret; secret: ISecret;
key: string; key: string;
@@ -66,24 +66,24 @@ export const repackageSecretToRaw = ({
ciphertext: secret.secretKeyCiphertext, ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV, iv: secret.secretKeyIV,
tag: secret.secretKeyTag, tag: secret.secretKeyTag,
key key,
}); });
const secretValue = decryptSymmetric128BitHexKeyUTF8({ const secretValue = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretValueCiphertext, ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV, iv: secret.secretValueIV,
tag: secret.secretValueTag, tag: secret.secretValueTag,
key key,
}); });
let secretComment: string = ''; let secretComment = "";
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
secretComment = decryptSymmetric128BitHexKeyUTF8({ secretComment = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretCommentCiphertext, ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV, iv: secret.secretCommentIV,
tag: secret.secretCommentTag, tag: secret.secretCommentTag,
key key,
}); });
} }
@@ -96,7 +96,7 @@ export const repackageSecretToRaw = ({
user: secret.user, user: secret.user,
secretKey, secretKey,
secretValue, secretValue,
secretComment secretComment,
}); });
} }
@@ -944,6 +944,6 @@ export const deleteSecretHelper = async ({
return ({ return ({
secrets, secrets,
secret secret,
}); });
}; };
+17 -17
View File
@@ -1,10 +1,10 @@
import { IUser } from '../models'; import { IUser } from "../models";
import { createOrganization } from './organization'; import { createOrganization } from "./organization";
import { addMembershipsOrg } from './membershipOrg'; import { addMembershipsOrg } from "./membershipOrg";
import { OWNER, ACCEPTED } from '../variables'; import { ACCEPTED, OWNER } from "../variables";
import { sendMail } from '../helpers/nodemailer'; import { sendMail } from "../helpers/nodemailer";
import { TokenService } from '../services'; import { TokenService } from "../services";
import { TOKEN_EMAIL_CONFIRMATION } from '../variables'; import { TOKEN_EMAIL_CONFIRMATION } from "../variables";
/** /**
* Send magic link to verify email to [email] * Send magic link to verify email to [email]
@@ -16,17 +16,17 @@ import { TOKEN_EMAIL_CONFIRMATION } from '../variables';
export const sendEmailVerification = async ({ email }: { email: string }) => { export const sendEmailVerification = async ({ email }: { email: string }) => {
const token = await TokenService.createToken({ const token = await TokenService.createToken({
type: TOKEN_EMAIL_CONFIRMATION, type: TOKEN_EMAIL_CONFIRMATION,
email email,
}); });
// send mail // send mail
await sendMail({ await sendMail({
template: 'emailVerification.handlebars', template: "emailVerification.handlebars",
subjectLine: 'Infisical confirmation code', subjectLine: "Infisical confirmation code",
recipients: [email], recipients: [email],
substitutions: { substitutions: {
code: token code: token,
} },
}); });
}; };
@@ -38,7 +38,7 @@ export const sendEmailVerification = async ({ email }: { email: string }) => {
*/ */
export const checkEmailVerification = async ({ export const checkEmailVerification = async ({
email, email,
code code,
}: { }: {
email: string; email: string;
code: string; code: string;
@@ -46,7 +46,7 @@ export const checkEmailVerification = async ({
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_CONFIRMATION, type: TOKEN_EMAIL_CONFIRMATION,
email, email,
token: code token: code,
}); });
}; };
@@ -59,7 +59,7 @@ export const checkEmailVerification = async ({
*/ */
export const initializeDefaultOrg = async ({ export const initializeDefaultOrg = async ({
organizationName, organizationName,
user user,
}: { }: {
organizationName: string; organizationName: string;
user: IUser; user: IUser;
@@ -69,14 +69,14 @@ export const initializeDefaultOrg = async ({
// subscription // subscription
const organization = await createOrganization({ const organization = await createOrganization({
email: user.email, email: user.email,
name: organizationName name: organizationName,
}); });
await addMembershipsOrg({ await addMembershipsOrg({
userIds: [user._id.toString()], userIds: [user._id.toString()],
organizationId: organization._id.toString(), organizationId: organization._id.toString(),
roles: [OWNER], roles: [OWNER],
statuses: [ACCEPTED] statuses: [ACCEPTED],
}); });
} catch (err) { } catch (err) {
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`); throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
+12 -12
View File
@@ -1,8 +1,8 @@
import { import {
IUser, IUser,
User, User,
} from '../models'; } from "../models";
import { sendMail } from './nodemailer'; import { sendMail } from "./nodemailer";
/** /**
* Initialize a user under email [email] * Initialize a user under email [email]
@@ -12,7 +12,7 @@ import { sendMail } from './nodemailer';
*/ */
export const setupAccount = async ({ email }: { email: string }) => { export const setupAccount = async ({ email }: { email: string }) => {
const user = await new User({ const user = await new User({
email email,
}).save(); }).save();
return user; return user;
@@ -49,7 +49,7 @@ export const completeAccount = async ({
encryptedPrivateKeyIV, encryptedPrivateKeyIV,
encryptedPrivateKeyTag, encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
}: { }: {
userId: string; userId: string;
firstName: string; firstName: string;
@@ -66,7 +66,7 @@ export const completeAccount = async ({
verifier: string; verifier: string;
}) => { }) => {
const options = { const options = {
new: true new: true,
}; };
const user = await User.findByIdAndUpdate( const user = await User.findByIdAndUpdate(
userId, userId,
@@ -82,7 +82,7 @@ export const completeAccount = async ({
iv: encryptedPrivateKeyIV, iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag, tag: encryptedPrivateKeyTag,
salt, salt,
verifier verifier,
}, },
options options
); );
@@ -100,7 +100,7 @@ export const completeAccount = async ({
export const checkUserDevice = async ({ export const checkUserDevice = async ({
user, user,
ip, ip,
userAgent userAgent,
}: { }: {
user: IUser; user: IUser;
ip: string; ip: string;
@@ -114,22 +114,22 @@ export const checkUserDevice = async ({
user.devices = user.devices.concat([{ user.devices = user.devices.concat([{
ip: String(ip), ip: String(ip),
userAgent userAgent,
}]); }]);
await user.save(); await user.save();
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'newDevice.handlebars', template: "newDevice.handlebars",
subjectLine: `Successful login from new device`, subjectLine: "Successful login from new device",
recipients: [user.email], recipients: [user.email],
substitutions: { substitutions: {
email: user.email, email: user.email,
timestamp: new Date().toString(), timestamp: new Date().toString(),
ip, ip,
userAgent userAgent,
} },
}); });
} }
} }
+16 -16
View File
@@ -1,13 +1,13 @@
import { import {
Workspace,
Bot, Bot,
Membership,
Key, Key,
Secret Membership,
} from '../models'; Secret,
import { createBot } from '../helpers/bot'; Workspace,
import { EELicenseService } from '../ee/services'; } from "../models";
import { SecretService } from '../services'; import { createBot } from "../helpers/bot";
import { EELicenseService } from "../ee/services";
import { SecretService } from "../services";
/** /**
* Create a workspace with name [name] in organization with id [organizationId] * Create a workspace with name [name] in organization with id [organizationId]
@@ -18,7 +18,7 @@ import { SecretService } from '../services';
*/ */
export const createWorkspace = async ({ export const createWorkspace = async ({
name, name,
organizationId organizationId,
}: { }: {
name: string; name: string;
organizationId: string; organizationId: string;
@@ -27,18 +27,18 @@ export const createWorkspace = async ({
const workspace = await new Workspace({ const workspace = await new Workspace({
name, name,
organization: organizationId, organization: organizationId,
autoCapitalization: true autoCapitalization: true,
}).save(); }).save();
// initialize bot for workspace // initialize bot for workspace
await createBot({ await createBot({
name: 'Infisical Bot', name: "Infisical Bot",
workspaceId: workspace._id workspaceId: workspace._id,
}); });
// initialize blind index salt for workspace // initialize blind index salt for workspace
await SecretService.createSecretBlindIndexData({ await SecretService.createSecretBlindIndexData({
workspaceId: workspace._id workspaceId: workspace._id,
}); });
await EELicenseService.refreshPlan(organizationId); await EELicenseService.refreshPlan(organizationId);
@@ -55,15 +55,15 @@ export const createWorkspace = async ({
export const deleteWorkspace = async ({ id }: { id: string }) => { export const deleteWorkspace = async ({ id }: { id: string }) => {
await Workspace.deleteOne({ _id: id }); await Workspace.deleteOne({ _id: id });
await Bot.deleteOne({ await Bot.deleteOne({
workspace: id workspace: id,
}); });
await Membership.deleteMany({ await Membership.deleteMany({
workspace: id workspace: id,
}); });
await Secret.deleteMany({ await Secret.deleteMany({
workspace: id workspace: id,
}); });
await Key.deleteMany({ await Key.deleteMany({
workspace: id workspace: id,
}); });
}; };
+18 -18
View File
@@ -2,7 +2,7 @@ import dotenv from "dotenv";
dotenv.config(); dotenv.config();
import express from "express"; import express from "express";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
require('express-async-errors'); require("express-async-errors");
import helmet from "helmet"; import helmet from "helmet";
import cors from "cors"; import cors from "cors";
import { DatabaseService } from "./services"; import { DatabaseService } from "./services";
@@ -15,32 +15,32 @@ const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
import { apiLimiter } from "./helpers/rateLimiter"; import { apiLimiter } from "./helpers/rateLimiter";
import { import {
workspace as eeWorkspaceRouter, action as eeActionRouter,
cloudProducts as eeCloudProductsRouter,
organizations as eeOrganizationsRouter,
secret as eeSecretRouter, secret as eeSecretRouter,
secretSnapshot as eeSecretSnapshotRouter, secretSnapshot as eeSecretSnapshotRouter,
action as eeActionRouter, workspace as eeWorkspaceRouter,
organizations as eeOrganizationsRouter,
cloudProducts as eeCloudProductsRouter,
} from "./ee/routes/v1"; } from "./ee/routes/v1";
import { import {
signup as v1SignupRouter,
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
organization as v1OrganizationRouter, integrationAuth as v1IntegrationAuthRouter,
workspace as v1WorkspaceRouter, integration as v1IntegrationRouter,
inviteOrg as v1InviteOrgRouter,
key as v1KeyRouter,
membershipOrg as v1MembershipOrgRouter, membershipOrg as v1MembershipOrgRouter,
membership as v1MembershipRouter, membership as v1MembershipRouter,
key as v1KeyRouter, organization as v1OrganizationRouter,
inviteOrg as v1InviteOrgRouter,
user as v1UserRouter,
userAction as v1UserActionRouter,
secret as v1SecretRouter,
serviceToken as v1ServiceTokenRouter,
password as v1PasswordRouter, password as v1PasswordRouter,
stripe as v1StripeRouter, secret as v1SecretRouter,
integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter,
secretsFolder as v1SecretsFolder, secretsFolder as v1SecretsFolder,
serviceToken as v1ServiceTokenRouter,
signup as v1SignupRouter,
stripe as v1StripeRouter,
userAction as v1UserActionRouter,
user as v1UserRouter,
workspace as v1WorkspaceRouter,
} from "./routes/v1"; } from "./routes/v1";
import { import {
signup as v2SignupRouter, signup as v2SignupRouter,
@@ -95,7 +95,7 @@ const main = async () => {
app.use((req, res, next) => { app.use((req, res, next) => {
// default to IP address provided by Cloudflare // default to IP address provided by Cloudflare
const cfIp = req.headers['cf-connecting-ip']; const cfIp = req.headers["cf-connecting-ip"];
req.realIP = Array.isArray(cfIp) ? cfIp[0] : (cfIp as string) || req.ip; req.realIP = Array.isArray(cfIp) ? cfIp[0] : (cfIp as string) || req.ip;
next(); next();
}); });
+19 -19
View File
@@ -2,32 +2,32 @@ import { Octokit } from "@octokit/rest";
import { IIntegrationAuth } from "../models"; import { IIntegrationAuth } from "../models";
import { standardRequest } from "../config/request"; import { standardRequest } from "../config/request";
import { import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE, INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER, INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_VERCEL, INTEGRATION_CHECKLY,
INTEGRATION_NETLIFY, INTEGRATION_CHECKLY_API_URL,
INTEGRATION_CIRCLECI,
INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_FLYIO,
INTEGRATION_FLYIO_API_URL,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_RAILWAY,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_API_URL, INTEGRATION_GITLAB_API_URL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_HEROKU,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_NETLIFY,
INTEGRATION_NETLIFY_API_URL, INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL, INTEGRATION_RAILWAY,
INTEGRATION_RAILWAY_API_URL, INTEGRATION_RAILWAY_API_URL,
INTEGRATION_FLYIO_API_URL, INTEGRATION_RENDER,
INTEGRATION_CIRCLECI_API_URL, INTEGRATION_RENDER_API_URL,
INTEGRATION_TRAVISCI_API_URL, INTEGRATION_SUPABASE,
INTEGRATION_SUPABASE_API_URL, INTEGRATION_SUPABASE_API_URL,
INTEGRATION_CHECKLY_API_URL INTEGRATION_TRAVISCI,
INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_VERCEL,
INTEGRATION_VERCEL_API_URL,
} from "../variables"; } from "../variables";
interface App { interface App {
@@ -214,7 +214,7 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage), per_page: String(perPage),
filter: 'all' filter: "all",
}); });
const { data } = await standardRequest.get( const { data } = await standardRequest.get(
+16 -16
View File
@@ -1,31 +1,31 @@
import { standardRequest } from "../config/request"; import { standardRequest } from "../config/request";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_GITHUB,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL, INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB,
INTEGRATION_GITLAB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL,
INTEGRATION_HEROKU,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_NETLIFY,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_VERCEL,
INTEGRATION_VERCEL_TOKEN_URL,
} from "../variables"; } from "../variables";
import { import {
getSiteURL,
getClientIdAzure, getClientIdAzure,
getClientSecretAzure,
getClientSecretHeroku,
getClientIdVercel,
getClientSecretVercel,
getClientIdNetlify,
getClientSecretNetlify,
getClientIdGitHub, getClientIdGitHub,
getClientSecretGitHub,
getClientIdGitLab, getClientIdGitLab,
getClientIdNetlify,
getClientIdVercel,
getClientSecretAzure,
getClientSecretGitHub,
getClientSecretGitLab, getClientSecretGitLab,
getClientSecretHeroku,
getClientSecretNetlify,
getClientSecretVercel,
getSiteURL,
} from "../config"; } from "../config";
interface ExchangeCodeAzureResponse { interface ExchangeCodeAzureResponse {
+7 -7
View File
@@ -1,9 +1,9 @@
import { exchangeCode } from './exchange'; import { exchangeCode } from "./exchange";
import { exchangeRefresh } from './refresh'; import { exchangeRefresh } from "./refresh";
import { getApps } from './apps'; import { getApps } from "./apps";
import { getTeams } from './teams'; import { getTeams } from "./teams";
import { syncSecrets } from './sync'; import { syncSecrets } from "./sync";
import { revokeAccess } from './revoke'; import { revokeAccess } from "./revoke";
export { export {
exchangeCode, exchangeCode,
@@ -11,5 +11,5 @@ export {
getApps, getApps,
getTeams, getTeams,
syncSecrets, syncSecrets,
revokeAccess revokeAccess,
} }

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