Merge branch 'Infisical:main' into cloudflare-workers

This commit is contained in:
Shreyas
2023-11-22 10:41:01 -08:00
committed by GitHub
125 changed files with 6243 additions and 1696 deletions
+2997 -32
View File
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -8,6 +8,7 @@
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.77.0", "@sentry/node": "^7.77.0",
"@sentry/tracing": "^7.48.0", "@sentry/tracing": "^7.48.0",
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
"@ucast/mongo2js": "^1.3.4", "@ucast/mongo2js": "^1.3.4",
@@ -41,13 +42,14 @@
"nanoid": "^3.3.6", "nanoid": "^3.3.6",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"ora": "^5.4.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-github": "^1.1.0", "passport-github": "^1.1.0",
"passport-gitlab2": "^5.0.0", "passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"pg": "^8.11.3",
"pino": "^8.16.1", "pino": "^8.16.1",
"pino-http": "^8.5.1", "pino-http": "^8.5.1",
"pg": "^8.11.3",
"posthog-node": "^2.6.0", "posthog-node": "^2.6.0",
"probot": "^12.3.1", "probot": "^12.3.1",
"query-string": "^7.1.3", "query-string": "^7.1.3",
@@ -70,7 +72,7 @@
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
"start": "node build/index.js", "start": "node build/index.js",
"dev": "nodemon index.js | pino-pretty --colorize", "dev": "nodemon index.js",
"swagger-autogen": "node ./swagger/index.ts", "swagger-autogen": "node ./swagger/index.ts",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
@@ -95,6 +97,8 @@
"devDependencies": { "devDependencies": {
"@jest/globals": "^29.3.1", "@jest/globals": "^29.3.1",
"@posthog/plugin-scaffold": "^1.3.4", "@posthog/plugin-scaffold": "^1.3.4",
"@swc/core": "^1.3.99",
"@swc/helpers": "^0.5.3",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/bull": "^4.10.0", "@types/bull": "^4.10.0",
@@ -125,6 +129,7 @@
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
"npm": "^8.19.3", "npm": "^8.19.3",
"pino-pretty": "^10.2.3", "pino-pretty": "^10.2.3",
"regenerator-runtime": "^0.14.0",
"smee-client": "^1.2.3", "smee-client": "^1.2.3",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"swagger-autogen": "^2.23.5", "swagger-autogen": "^2.23.5",
+43
View File
@@ -0,0 +1,43 @@
import ora from "ora";
import nodemailer from "nodemailer";
import { getSmtpHost, getSmtpPort } from "./config";
import { logger } from "./utils/logging";
import mongoose from "mongoose";
import { redisClient } from "./services/RedisService";
type BootstrapOpt = {
transporter: nodemailer.Transporter;
};
export const bootstrap = async ({ transporter }: BootstrapOpt) => {
const spinner = ora().start();
spinner.info("Checking configurations...");
spinner.info("Testing smtp connection");
await transporter
.verify()
.then(async () => {
spinner.succeed("SMTP successfully connected");
})
.catch(async (err) => {
spinner.fail(`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`);
logger.error(err);
});
spinner.info("Testing mongodb connection");
if (mongoose.connection.readyState !== mongoose.ConnectionStates.connected) {
spinner.fail("Mongo DB - Failed to connect");
} else {
spinner.succeed("Mongodb successfully connected");
}
spinner.info("Testing redis connection");
const redisPing = await redisClient?.ping();
if (!redisPing) {
spinner.fail("Redis - Failed to connect");
} else {
spinner.succeed("Redis successfully connected");
}
spinner.stop();
};
+129 -57
View File
@@ -3,99 +3,171 @@ import { GITLAB_URL } from "../variables";
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 () =>
export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true";
export const getAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? (await client.getSecret("AUTH_SECRET")).secretValue; export const getSaltRounds = async () =>
export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10;
export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; export const getAuthSecret = async () =>
export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; (await client.getSecret("JWT_AUTH_SECRET")).secretValue ??
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1) (await client.getSecret("AUTH_SECRET")).secretValue;
export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; export const getJwtAuthLifetime = async () =>
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d";
export const getJwtMfaLifetime = async () =>
(await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtRefreshLifetime = async () =>
(await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d";
export const getJwtServiceSecret = async () =>
(await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1)
export const getJwtSignupLifetime = async () =>
(await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
export const getJwtProviderAuthLifetime = async () =>
(await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
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 () =>
export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; (await client.getSecret("NODE_ENV")).secretValue || "production";
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 () =>
export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_VERCEL")).secretValue; (await client.getSecret("CLIENT_ID_HEROKU")).secretValue;
export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; export const getClientIdVercel = async () =>
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; (await client.getSecret("CLIENT_ID_VERCEL")).secretValue;
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; export const getClientIdNetlify = async () =>
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue;
export const getClientIdGCPSecretManager = async () => (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue; export const getClientIdGitHub = async () =>
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; export const getClientIdGitLab = async () =>
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue; (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; export const getClientIdBitBucket = async () =>
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; export const getClientIdGCPSecretManager = async () =>
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue;
export const getClientSecretGCPSecretManager = async () => (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue; export const getClientSecretAzure = async () =>
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; (await client.getSecret("CLIENT_SECRET_AZURE")).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 getClientSecretNetlify = async () =>
(await client.getSecret("CLIENT_SECRET_NETLIFY")).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 getClientSecretBitBucket = async () =>
(await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
export const getClientSecretGCPSecretManager = async () =>
(await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue;
export const getClientSlugVercel = async () =>
(await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; export const getClientIdGoogleLogin = async () =>
export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue;
export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; export const getClientSecretGoogleLogin = async () =>
export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue;
export const getClientIdGitLabLogin = async () => (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue; export const getClientIdGitHubLogin = async () =>
export const getClientSecretGitLabLogin = async () => (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue; (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue;
export const getUrlGitLabLogin = async () => (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL; export const getClientSecretGitHubLogin = async () =>
(await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue;
export const getClientIdGitLabLogin = async () =>
(await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue;
export const getClientSecretGitLabLogin = async () =>
(await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue;
export const getUrlGitLabLogin = async () =>
(await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL;
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; export const getAwsCloudWatchLog = async () => {
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; const logGroupName =
(await client.getSecret("AWS_CLOUDWATCH_LOG_GROUP_NAME")).secretValue || "infisical-log-stream";
const region = (await client.getSecret("AWS_CLOUDWATCH_LOG_REGION")).secretValue;
const accessKeyId = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID")).secretValue;
const accessKeySecret = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET"))
.secretValue;
const interval = parseInt(
(await client.getSecret("AWS_CLOUDWATCH_LOG_INTERVAL")).secretValue || 1000,
10
);
if (!region || !accessKeyId || !accessKeySecret) return;
return { logGroupName, region, accessKeySecret, accessKeyId, interval };
};
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 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 () =>
export const getSmtpPort = async () => parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587; (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false;
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 () =>
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical"; (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
export const getSmtpFromName = async () =>
(await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue; export const getSecretScanningWebhookProxy = async () =>
export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue; (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue;
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue; export const getSecretScanningWebhookSecret = async () =>
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue; (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue;
export const getSecretScanningGitAppId = async () =>
(await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
export const getSecretScanningPrivateKey = async () =>
(await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue; export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue;
export const getIsInfisicalCloud = async () =>
(await client.getSecret("INFISICAL_CLOUD")).secretValue === "true";
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";
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;
} };
+24
View File
@@ -0,0 +1,24 @@
import { IServerConfig, ServerConfig } from "../models/serverConfig";
let serverConfig: IServerConfig;
export const serverConfigInit = async () => {
const cfg = await ServerConfig.findOne({});
if (!cfg) {
const cfg = new ServerConfig();
await cfg.save();
serverConfig = cfg;
} else {
serverConfig = cfg;
}
return serverConfig;
};
export const getServerConfig = () => serverConfig;
export const updateServerConfig = async (data: Partial<IServerConfig>) => {
const cfg = await ServerConfig.findByIdAndUpdate(serverConfig._id, data, { new: true });
if (!cfg) throw new Error("Failed to update server config");
serverConfig = cfg;
return serverConfig;
};
@@ -0,0 +1,100 @@
import { Request, Response } from "express";
import { getHttpsEnabled } from "../../config";
import { getServerConfig, updateServerConfig as setServerConfig } from "../../config/serverConfig";
import { initializeDefaultOrg, issueAuthTokens } from "../../helpers";
import { validateRequest } from "../../helpers/validation";
import { User } from "../../models";
import { TelemetryService } from "../../services";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/admin";
export const getServerConfigInfo = (_req: Request, res: Response) => {
const config = getServerConfig();
return res.send({ config });
};
export const updateServerConfig = async (req: Request, res: Response) => {
const {
body: { allowSignUp }
} = await validateRequest(reqValidator.UpdateServerConfigV1, req);
const config = await setServerConfig({ allowSignUp });
return res.send({ config });
};
export const adminSignUp = async (req: Request, res: Response) => {
const cfg = getServerConfig();
if (cfg.initialized) throw UnauthorizedRequestError({ message: "Admin has been created" });
const {
body: {
email,
publicKey,
salt,
lastName,
verifier,
firstName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag
}
} = await validateRequest(reqValidator.SignupV1, req);
let user = await User.findOne({ email });
if (user) throw BadRequestError({ message: "User already exist" });
user = new User({
email,
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier,
superAdmin: true
});
await user.save();
await initializeDefaultOrg({ organizationName: "Admin Org", user });
await setServerConfig({ initialized: true });
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? ""
});
const token = tokens.token;
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "admin initialization",
properties: {
email: user.email,
lastName,
firstName
}
});
}
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled()
});
return res.status(200).send({
message: "Successfully set up admin account",
user,
token
});
};
+11 -3
View File
@@ -7,7 +7,7 @@ import * as reqValidator from "../../validation/bot";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
@@ -28,7 +28,11 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req); } = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -70,7 +74,11 @@ export const setBotActiveState = async (req: Request, res: Response) => {
} }
const userId = req.user._id; const userId = req.user._id;
const { permission } = await getUserProjectPermissions(userId, bot.workspace.toString()); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: bot.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
+4 -1
View File
@@ -16,6 +16,8 @@ import * as workspaceController from "./workspaceController";
import * as secretScanningController from "./secretScanningController"; import * as secretScanningController from "./secretScanningController";
import * as webhookController from "./webhookController"; import * as webhookController from "./webhookController";
import * as secretImpsController from "./secretImpsController"; import * as secretImpsController from "./secretImpsController";
import * as adminController from "./adminController";
export { export {
authController, authController,
botController, botController,
@@ -34,5 +36,6 @@ export {
workspaceController, workspaceController,
secretScanningController, secretScanningController,
webhookController, webhookController,
secretImpsController secretImpsController,
adminController
}; };
@@ -25,7 +25,7 @@ import * as reqValidator from "../../validation/integrationAuth";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { getIntegrationAuthAccessHelper } from "../../helpers"; import { getIntegrationAuthAccessHelper } from "../../helpers";
@@ -40,15 +40,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
const integrationAuth = await IntegrationAuth.findById(integrationAuthId); const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth) if (!integrationAuth) return res.status(400).send({
return res.status(400).send({ message: "Failed to find integration authorization"
message: "Failed to find integration authorization" });
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -79,7 +79,11 @@ export const oAuthExchange = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.OauthExchangeV1, req); } = await validateRequest(reqValidator.OauthExchangeV1, req);
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration"); if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -131,7 +135,11 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken } body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req); } = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -224,11 +232,12 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -263,10 +272,11 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -299,10 +309,11 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -360,10 +371,11 @@ export const getIntegrationAuthChecklyGroups = async (req: Request, res: Respons
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -413,10 +425,11 @@ export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response)
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -465,10 +478,11 @@ export const getIntegrationAuthQoveryProjects = async (req: Request, res: Respon
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -526,10 +540,11 @@ export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Re
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -587,10 +602,11 @@ export const getIntegrationAuthQoveryApps = async (req: Request, res: Response)
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -648,10 +664,11 @@ export const getIntegrationAuthQoveryContainers = async (req: Request, res: Resp
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -709,10 +726,11 @@ export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response)
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -771,10 +789,11 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -864,10 +883,11 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -988,10 +1008,11 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -1044,10 +1065,11 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -1132,10 +1154,11 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -1201,10 +1224,11 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
integrationAuthId: new Types.ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -13,7 +13,7 @@ import * as reqValidator from "../../validation/integration";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@@ -51,11 +51,12 @@ export const createIntegration = async (req: Request, res: Response) => {
); );
if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" }); if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace._id.toString() workspaceId: integrationAuth.workspace._id
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -164,10 +165,11 @@ export const updateIntegration = async (req: Request, res: Response) => {
const integration = await Integration.findById(integrationId); const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" }); if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integration.workspace.toString() workspaceId: integration.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -234,10 +236,11 @@ export const deleteIntegration = async (req: Request, res: Response) => {
const integration = await Integration.findById(integrationId); const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" }); if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integration.workspace.toString() workspaceId: integration.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -285,7 +288,11 @@ export const manualSync = async (req: Request, res: Response) => {
body: { workspaceId, environment } body: { workspaceId, environment }
} = await validateRequest(reqValidator.ManualSyncV1, req); } = await validateRequest(reqValidator.ManualSyncV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
+6 -2
View File
@@ -9,7 +9,7 @@ import * as reqValidator from "../../validation/key";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@@ -26,7 +26,11 @@ export const uploadKey = async (req: Request, res: Response) => {
body: { key } body: { key }
} = await validateRequest(reqValidator.UploadKeyV1, req); } = await validateRequest(reqValidator.UploadKeyV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -12,7 +12,7 @@ import * as reqValidator from "../../validation/membership";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
@@ -63,11 +63,12 @@ export const deleteMembership = async (req: Request, res: Response) => {
if (!membershipToDelete) { if (!membershipToDelete) {
throw new Error("Failed to delete workspace membership that doesn't exist"); throw new Error("Failed to delete workspace membership that doesn't exist");
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
membershipToDelete.workspace.toString() workspaceId: membershipToDelete.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -118,10 +119,11 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
throw new Error("Failed to find membership to change role"); throw new Error("Failed to find membership to change role");
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
membershipToChangeRole.workspace.toString() workspaceId: membershipToChangeRole.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -191,7 +193,12 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
params: { workspaceId }, params: { workspaceId },
body: { email } body: { email }
} = await validateRequest(InviteUserToWorkspaceV1, req); } = await validateRequest(InviteUserToWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -1,4 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose";
import { isValidScope } from "../../helpers"; import { isValidScope } from "../../helpers";
import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models"; import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
@@ -15,7 +17,7 @@ import * as reqValidator from "../../validation/secretImports";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
@@ -105,7 +107,11 @@ export const createSecretImp = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -313,10 +319,11 @@ export const updateSecretImport = async (req: Request, res: Response) => {
} }
} else { } else {
// non token entry check // non token entry check
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
importSecDoc.workspace.toString() workspaceId: importSecDoc.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@@ -442,10 +449,11 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
importSecDoc.workspace.toString() workspaceId: importSecDoc.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@@ -550,7 +558,11 @@ export const getSecretImports = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@@ -604,7 +616,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@@ -657,10 +673,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
permissionCheckFn = (env: string, secPath: string) => permissionCheckFn = (env: string, secPath: string) =>
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath); isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
} else { } else {
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
importSecDoc.workspace.toString() workspaceId: importSecDoc.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@@ -17,7 +17,7 @@ import {
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/folders"; import * as reqValidator from "../../validation/folders";
@@ -125,7 +125,11 @@ export const createFolder = async (req: Request, res: Response) => {
} }
} else { } else {
// user check // user check
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -332,7 +336,11 @@ export const updateFolderById = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -502,7 +510,11 @@ export const deleteFolder = async (req: Request, res: Response) => {
} }
} else { } else {
// check that user is a member of the workspace // check that user is a member of the workspace
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -649,7 +661,10 @@ export const getFolders = async (req: Request, res: Response) => {
} }
} else { } else {
// check that user is a member of the workspace // check that user is a member of the workspace
await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
} }
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
@@ -2,10 +2,8 @@ import { Request, Response } from "express";
import { AuthMethod, User } from "../../models"; import { AuthMethod, User } from "../../models";
import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup"; import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup";
import { createToken } from "../../helpers/auth"; import { createToken } from "../../helpers/auth";
import { BadRequestError } from "../../utils/errors";
import { import {
getAuthSecret, getAuthSecret,
getInviteOnlySignup,
getJwtSignupLifetime, getJwtSignupLifetime,
getSmtpConfigured getSmtpConfigured
} from "../../config"; } from "../../config";
@@ -68,16 +66,6 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
}); });
} }
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
const userCount = await User.countDocuments({});
if (userCount != 0) {
throw BadRequestError({
message: "New user sign ups are not allowed at this time. You must be invited to sign up."
});
}
}
// verify email // verify email
if (await getSmtpConfigured()) { if (await getSmtpConfigured()) {
await checkEmailVerification({ await checkEmailVerification({
+27 -17
View File
@@ -16,7 +16,7 @@ import * as reqValidator from "../../validation/webhooks";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
@@ -26,7 +26,11 @@ export const createWebhook = async (req: Request, res: Response) => {
body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.CreateWebhookV1, req); } = await validateRequest(reqValidator.CreateWebhookV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@@ -98,11 +102,11 @@ export const updateWebhook = async (req: Request, res: Response) => {
if (!webhook) { if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" }); throw BadRequestError({ message: "Webhook not found!!" });
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
webhook.workspace.toString() workspaceId: webhook.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@@ -146,10 +150,11 @@ export const deleteWebhook = async (req: Request, res: Response) => {
throw ResourceNotFoundError({ message: "Webhook not found!!" }); throw ResourceNotFoundError({ message: "Webhook not found!!" });
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
webhook.workspace.toString() workspaceId: webhook.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@@ -193,10 +198,11 @@ export const testWebhook = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Webhook not found!!" }); throw BadRequestError({ message: "Webhook not found!!" });
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
webhook.workspace.toString() workspaceId: webhook.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@@ -236,8 +242,12 @@ export const listWebhooks = async (req: Request, res: Response) => {
const { const {
query: { environment, workspaceId, secretPath } query: { environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.ListWebhooksV1, req); } = await validateRequest(reqValidator.ListWebhooksV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@@ -25,7 +25,7 @@ import * as reqValidator from "../../validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
/** /**
@@ -39,7 +39,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req); } = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -72,7 +76,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req); } = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -195,7 +203,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.DeleteWorkspaceV1, req); } = await validateRequest(reqValidator.DeleteWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Workspace ProjectPermissionSub.Workspace
@@ -223,7 +235,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
body: { name } body: { name }
} = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req); } = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Workspace ProjectPermissionSub.Workspace
@@ -257,7 +273,12 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req); } = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -283,7 +304,11 @@ export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: R
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req); } = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@@ -309,7 +334,11 @@ export const getWorkspaceServiceTokens = async (req: Request, res: Response) =>
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req); } = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens
@@ -12,14 +12,12 @@ import {
import { EventType, SecretVersion } from "../../ee/models"; import { EventType, SecretVersion } from "../../ee/models";
import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { EEAuditLogService, EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors"; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from "lodash";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/environments"; import * as reqValidator from "../../validation/environments";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { SecretImport } from "../../models"; import { SecretImport } from "../../models";
@@ -114,7 +112,11 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentName, environmentSlug } body: { environmentName, environmentSlug }
} = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req); } = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@@ -191,7 +193,11 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName } body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName }
} = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req); } = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@@ -322,7 +328,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentName, environmentSlug, oldEnvironmentSlug } body: { environmentName, environmentSlug, oldEnvironmentSlug }
} = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req); } = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@@ -511,7 +521,11 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentSlug } body: { environmentSlug }
} = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req); } = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@@ -587,98 +601,4 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug environment: environmentSlug
}); });
}; };
// TODO(akhilmhdh) after rbac this can be completely removed
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Get all accessible environments of a workspace'
#swagger.description = 'Fetch all environments that the user has access to in a specified workspace'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of the workspace",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessibleEnvironments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Development"
},
"slug": {
"type": "string",
"example": "development"
},
"isWriteDenied": {
"type": "boolean",
"example": false
},
"isReadDenied": {
"type": "boolean",
"example": false
}
}
}
}
},
"description": "List of environments the user has access to in the specified workspace"
}
}
}
}
*/
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetAllAccessibileEnvironmentsOfWorkspaceV2, req);
const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions(
req.user._id,
workspaceId
);
const accessibleEnvironments: any = [];
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions;
const relatedWorkspace = await Workspace.findById(workspaceId);
if (!relatedWorkspace) {
throw BadRequestError();
}
relatedWorkspace.environments.forEach((environment) => {
const isReadBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_READ_SECRETS
});
const isWriteBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_WRITE_SECRETS
});
if (isReadBlocked && isWriteBlocked) {
return;
} else {
accessibleEnvironments.push({
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked
});
}
});
res.json({ accessibleEnvironments });
};
@@ -8,7 +8,7 @@ import { EEAuditLogService } from "../../ee/services";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { sendMail } from "../../helpers"; import { sendMail } from "../../helpers";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
@@ -27,7 +27,11 @@ export const addUserToWorkspace = async (req: Request, res: Response) => {
if (!workspace) throw new Error("Failed to find workspace"); if (!workspace) throw new Error("Failed to find workspace");
// check permission // check permission
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -1,24 +1,16 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { Membership, MembershipOrg, Workspace } from "../../models";
Membership,
MembershipOrg,
Workspace
} from "../../models";
import { Role } from "../../ee/models"; import { Role } from "../../ee/models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg"; import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { import {
createOrganization as create, createOrganization as create,
deleteOrganization, deleteOrganization,
updateSubscriptionOrgQuantity updateSubscriptionOrgQuantity
} from "../../helpers/organization"; } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg"; import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { import { ACCEPTED, ADMIN, CUSTOM } from "../../variables";
ACCEPTED,
ADMIN,
CUSTOM
} from "../../variables";
import * as reqValidator from "../../validation/organization"; import * as reqValidator from "../../validation/organization";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import { import {
@@ -155,7 +147,7 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
OrgPermissionSubjects.Member OrgPermissionSubjects.Member
); );
const isCustomRole = !["admin", "member", "owner"].includes(role); const isCustomRole = !["admin", "member"].includes(role);
if (isCustomRole) { if (isCustomRole) {
const orgRole = await Role.findOne({ slug: role, isOrgRole: true }); const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
if (!orgRole) throw BadRequestError({ message: "Role not found" }); if (!orgRole) throw BadRequestError({ message: "Role not found" });
@@ -336,7 +328,7 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
* @param res * @param res
* @returns * @returns
*/ */
export const createOrganization = async (req: Request, res: Response) => { export const createOrganization = async (req: Request, res: Response) => {
const { const {
body: { name } body: { name }
} = await validateRequest(reqValidator.CreateOrgv2, req); } = await validateRequest(reqValidator.CreateOrgv2, req);
@@ -361,27 +353,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
/** /**
* Delete organization with id [organizationId] * Delete organization with id [organizationId]
* @param req * @param req
* @param res * @param res
*/ */
export const deleteOrganizationById = async (req: Request, res: Response) => { export const deleteOrganizationById = async (req: Request, res: Response) => {
const { const {
params: { organizationId } params: { organizationId }
} = await validateRequest(reqValidator.DeleteOrgv2, req); } = await validateRequest(reqValidator.DeleteOrgv2, req);
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
user: req.user._id, user: req.user._id,
organization: new Types.ObjectId(organizationId), organization: new Types.ObjectId(organizationId),
role: ADMIN role: ADMIN
}); });
if (!membershipOrg) throw UnauthorizedRequestError(); if (!membershipOrg) throw UnauthorizedRequestError();
const organization = await deleteOrganization({ const organization = await deleteOrganization({
organizationId: new Types.ObjectId(organizationId) organizationId: new Types.ObjectId(organizationId)
}); });
return res.status(200).send({ return res.status(200).send({
organization organization
}); });
} };
@@ -39,7 +39,7 @@ import {
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
@@ -159,7 +159,11 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
// not using service token using auth // not using service token using auth
if (!(req.authData.authPayload instanceof ServiceTokenData)) { if (!(req.authData.authPayload instanceof ServiceTokenData)) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (createSecrets.length) if (createSecrets.length)
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
@@ -11,7 +11,7 @@ import * as reqValidator from "../../validation/serviceTokenData";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Types } from "mongoose"; import { Types } from "mongoose";
@@ -75,7 +75,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const { const {
body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv } body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv }
} = await validateRequest(reqValidator.CreateServiceTokenV2, req); } = await validateRequest(reqValidator.CreateServiceTokenV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens
@@ -151,10 +156,11 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId); let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId);
if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" }); if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
serviceTokenData.workspace.toString() workspaceId: serviceTokenData.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens
+17 -7
View File
@@ -7,7 +7,7 @@ import { validateRequest } from "../../helpers/validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import * as reqValidator from "../../validation/tags"; import * as reqValidator from "../../validation/tags";
@@ -17,7 +17,11 @@ export const createWorkspaceTag = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req); } = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Tags ProjectPermissionSub.Tags
@@ -45,10 +49,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
throw BadRequestError(); throw BadRequestError();
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
tagFromDB.workspace.toString() workspaceId: tagFromDB.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Tags ProjectPermissionSub.Tags
@@ -66,7 +71,12 @@ export const getWorkspaceTags = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceTagsV2, req); } = await validateRequest(reqValidator.GetWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Tags ProjectPermissionSub.Tags
@@ -16,7 +16,7 @@ import * as reqValidator from "../../validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@@ -272,7 +272,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req); } = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -352,7 +356,11 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
body: { role } body: { role }
} = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req); } = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -420,7 +428,11 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
params: { workspaceId, membershipId } params: { workspaceId, membershipId }
} = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req); } = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@@ -452,7 +464,11 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
body: { autoCapitalization } body: { autoCapitalization }
} = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req); } = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Settings ProjectPermissionSub.Settings
+243 -174
View File
@@ -3,11 +3,16 @@ import { Types } from "mongoose";
import { EventService, SecretService } from "../../services"; import { EventService, SecretService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { BotService } from "../../services"; import { BotService } from "../../services";
import { containsGlobPatterns, isValidScopeV3, repackageSecretToRaw } from "../../helpers/secrets"; import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { Folder, IMembership, IServiceTokenData, IServiceTokenDataV3 } from "../../models"; import {
import { Permission } from "../../models/serviceTokenDataV3"; Folder,
IServiceTokenData,
Membership,
ServiceTokenData,
User
} from "../../models";
import { getFolderByPath } from "../../services/FolderService"; import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
@@ -15,13 +20,10 @@ import * as reqValidator from "../../validation/secrets";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { import { validateServiceTokenDataClientForWorkspace } from "../../validation";
validateServiceTokenDataClientForWorkspace,
validateServiceTokenDataV3ClientForWorkspace
} from "../../validation";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables"; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
import { UnauthorizedRequestError } from "../../utils/errors"; import { UnauthorizedRequestError } from "../../utils/errors";
@@ -31,7 +33,7 @@ import {
getSecretPolicyOfBoard getSecretPolicyOfBoard
} from "../../ee/services/SecretApprovalService"; } from "../../ee/services/SecretApprovalService";
import { CommitType } from "../../ee/models/secretApprovalRequest"; import { CommitType } from "../../ee/models/secretApprovalRequest";
import { IRole } from "../../ee/models/role"; import { logger } from "../../utils/logging";
const checkSecretsPermission = async ({ const checkSecretsPermission = async ({
authData, authData,
@@ -47,36 +49,31 @@ const checkSecretsPermission = async ({
secretAction: ProjectPermissionActions; // CRUD secretAction: ProjectPermissionActions; // CRUD
}): Promise<{ }): Promise<{
authVerifier: (env: string, secPath: string) => boolean; authVerifier: (env: string, secPath: string) => boolean;
membership?: Omit<IMembership, "customRole"> & { customRole: IRole };
}> => { }> => {
let STV2RequiredPermissions = []; let STV2RequiredPermissions = [];
let STV3RequiredPermissions: Permission[] = [];
switch (secretAction) { switch (secretAction) {
case ProjectPermissionActions.Create: case ProjectPermissionActions.Create:
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS]; STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
STV3RequiredPermissions = [Permission.WRITE];
break; break;
case ProjectPermissionActions.Read: case ProjectPermissionActions.Read:
STV2RequiredPermissions = [PERMISSION_READ_SECRETS]; STV2RequiredPermissions = [PERMISSION_READ_SECRETS];
STV3RequiredPermissions = [Permission.READ];
break; break;
case ProjectPermissionActions.Edit: case ProjectPermissionActions.Edit:
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS]; STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
STV3RequiredPermissions = [Permission.WRITE];
break; break;
case ProjectPermissionActions.Delete: case ProjectPermissionActions.Delete:
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS]; STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
STV3RequiredPermissions = [Permission.WRITE];
break; break;
} }
switch (authData.actor.type) { switch (authData.actor.type) {
case ActorType.USER: { case ActorType.USER: {
const { permission, membership } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
authData.actor.metadata.userId, authData,
workspaceId workspaceId: new Types.ObjectId(workspaceId)
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
secretAction, secretAction,
subject(ProjectPermissionSub.Secrets, { environment, secretPath }) subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@@ -89,8 +86,7 @@ const checkSecretsPermission = async ({
environment: env, environment: env,
secretPath: secPath secretPath: secPath
}) })
), )
membership
}; };
} }
case ActorType.SERVICE: { case ActorType.SERVICE: {
@@ -104,22 +100,24 @@ const checkSecretsPermission = async ({
return { authVerifier: () => true }; return { authVerifier: () => true };
} }
case ActorType.SERVICE_V3: { case ActorType.SERVICE_V3: {
await validateServiceTokenDataV3ClientForWorkspace({ const { permission } = await getAuthDataProjectPermissions({
authData, authData,
serviceTokenData: authData.authPayload as IServiceTokenDataV3, workspaceId: new Types.ObjectId(workspaceId)
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: STV3RequiredPermissions
}); });
ForbiddenError.from(permission).throwUnlessCan(
secretAction,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
return { return {
authVerifier: (env: string, secPath: string) => authVerifier: (env: string, secPath: string) =>
isValidScopeV3({ permission.can(
authPayload: authData.authPayload as IServiceTokenDataV3, secretAction,
environment: env, subject(ProjectPermissionSub.Secrets, {
secretPath: secPath, environment: env,
requiredPermissions: STV3RequiredPermissions secretPath: secPath
}) })
)
}; };
} }
default: { default: {
@@ -199,17 +197,22 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
query: { include_imports: includeImports } query: { include_imports: includeImports }
} = validatedData; } = validatedData;
// if the service token has single scope, it will get all secrets for that scope by default logger.info(`getSecretsRaw: fetch raw secrets [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [includeImports=${includeImports}]`)
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
if ( if (req.authData.authPayload instanceof ServiceTokenData) {
serviceTokenDetails &&
serviceTokenDetails.scopes.length == 1 && // if the service token has single scope, it will get all secrets for that scope by default
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath) const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
) { if (
const scope = serviceTokenDetails.scopes[0]; serviceTokenDetails &&
secretPath = scope.secretPath; serviceTokenDetails.scopes.length == 1 &&
environment = scope.environment; !containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
workspaceId = serviceTokenDetails.workspace.toString(); ) {
const scope = serviceTokenDetails.scopes[0];
secretPath = scope.secretPath;
environment = scope.environment;
workspaceId = serviceTokenDetails.workspace.toString();
}
} }
if (!environment || !workspaceId) if (!environment || !workspaceId)
@@ -353,6 +356,8 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
params: { secretName } params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req); } = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
logger.info(`getSecretByNameRaw: fetch raw secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [include_imports=${include_imports}]`)
await checkSecretsPermission({ await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
@@ -476,6 +481,8 @@ export const createSecretRaw = async (req: Request, res: Response) => {
} }
} = await validateRequest(reqValidator.CreateSecretRawV3, req); } = await validateRequest(reqValidator.CreateSecretRawV3, req);
logger.info(`createSecretRaw: create a secret raw by name and value [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [skipMultilineEncoding=${skipMultilineEncoding}]`)
await checkSecretsPermission({ await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
@@ -621,6 +628,8 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
body: { workspaceId, environment, secretValue, secretPath, type, skipMultilineEncoding } body: { workspaceId, environment, secretValue, secretPath, type, skipMultilineEncoding }
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req); } = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
logger.info(`updateSecretByNameRaw: update raw secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [skipMultilineEncoding=${skipMultilineEncoding}]`)
await checkSecretsPermission({ await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
@@ -743,6 +752,8 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
body: { environment, secretPath, type, workspaceId } body: { environment, secretPath, type, workspaceId }
} = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req); } = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req);
logger.info(`deleteSecretByNameRaw: delete a secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}]`)
await checkSecretsPermission({ await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
@@ -796,6 +807,8 @@ export const getSecrets = async (req: Request, res: Response) => {
query: { secretPath } query: { secretPath }
} = validatedData; } = validatedData;
logger.info(`getSecrets: fetch encrypted secrets [environment=${environment}] [workspaceId=${workspaceId}] [includeImports=${includeImports}]`)
const { authVerifier: permissionCheckFn } = await checkSecretsPermission({ const { authVerifier: permissionCheckFn } = await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
@@ -850,6 +863,8 @@ export const getSecretByName = async (req: Request, res: Response) => {
params: { secretName } params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameV3, req); } = await validateRequest(reqValidator.GetSecretByNameV3, req);
logger.info(`getSecretByName: get a single secret by name [environment=${environment}] [workspaceId=${workspaceId}] [include_imports=${include_imports}] [type=${type}]`)
await checkSecretsPermission({ await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
@@ -900,7 +915,9 @@ export const createSecret = async (req: Request, res: Response) => {
params: { secretName } params: { secretName }
} = await validateRequest(reqValidator.CreateSecretV3, req); } = await validateRequest(reqValidator.CreateSecretV3, req);
const { membership } = await checkSecretsPermission({ logger.info(`createSecret: create an encrypted secret [environment=${environment}] [workspaceId=${workspaceId}] [skipMultilineEncoding=${skipMultilineEncoding}] [type=${type}]`)
await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
environment, environment,
@@ -908,35 +925,42 @@ export const createSecret = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Create secretAction: ProjectPermissionActions.Create
}); });
if (membership && type !== "personal") { if (req.authData.authPayload instanceof User) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); const membership = await Membership.findOne({
if (secretApprovalPolicy) { user: req.authData.authPayload._id,
const secretApprovalRequest = await generateSecretApprovalRequest({ workspace: new Types.ObjectId(workspaceId)
workspaceId, });
environment,
secretPath, if (membership && type !== "personal") {
policy: secretApprovalPolicy, const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
commiterMembershipId: membership._id.toString(), if (secretApprovalPolicy) {
authData: req.authData, const secretApprovalRequest = await generateSecretApprovalRequest({
data: { workspaceId,
[CommitType.CREATE]: [ environment,
{ secretPath,
secretName, policy: secretApprovalPolicy,
secretValueCiphertext, commiterMembershipId: membership._id.toString(),
secretValueIV, authData: req.authData,
secretValueTag, data: {
secretCommentIV, [CommitType.CREATE]: [
secretCommentTag, {
secretCommentCiphertext, secretName,
skipMultilineEncoding, secretValueCiphertext,
secretKeyTag, secretValueIV,
secretKeyCiphertext, secretValueTag,
secretKeyIV secretCommentIV,
} secretCommentTag,
] secretCommentCiphertext,
} skipMultilineEncoding,
}); secretKeyTag,
return res.send({ approval: secretApprovalRequest }); secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
} }
} }
@@ -1005,11 +1029,13 @@ export const updateSecretByName = async (req: Request, res: Response) => {
params: { secretName } params: { secretName }
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req); } = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
logger.info(`updateSecretByName: update a encrypted secret by name [environment=${environment}] [workspaceId=${workspaceId}] [skipMultilineEncoding=${skipMultilineEncoding}] [type=${type}]`)
if (newSecretName && (!secretKeyIV || !secretKeyTag || !secretKeyCiphertext)) { if (newSecretName && (!secretKeyIV || !secretKeyTag || !secretKeyCiphertext)) {
throw BadRequestError({ message: "Missing encrypted key" }); throw BadRequestError({ message: "Missing encrypted key" });
} }
const { membership } = await checkSecretsPermission({ await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
environment, environment,
@@ -1017,37 +1043,44 @@ export const updateSecretByName = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Edit secretAction: ProjectPermissionActions.Edit
}); });
if (membership && type !== "personal") { if (req.authData.authPayload instanceof User) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); const membership = await Membership.findOne({
if (secretApprovalPolicy) { user: req.authData.authPayload._id,
const secretApprovalRequest = await generateSecretApprovalRequest({ workspace: new Types.ObjectId(workspaceId)
workspaceId, });
environment,
secretPath, if (membership && type !== "personal") {
policy: secretApprovalPolicy, const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
commiterMembershipId: membership._id.toString(), if (secretApprovalPolicy) {
authData: req.authData, const secretApprovalRequest = await generateSecretApprovalRequest({
data: { workspaceId,
[CommitType.UPDATE]: [ environment,
{ secretPath,
secretName, policy: secretApprovalPolicy,
newSecretName, commiterMembershipId: membership._id.toString(),
secretValueCiphertext, authData: req.authData,
secretValueIV, data: {
secretValueTag, [CommitType.UPDATE]: [
tags, {
secretCommentIV, secretName,
secretCommentTag, newSecretName,
secretCommentCiphertext, secretValueCiphertext,
skipMultilineEncoding, secretValueIV,
secretKeyTag, secretValueTag,
secretKeyCiphertext, tags,
secretKeyIV secretCommentIV,
} secretCommentTag,
] secretCommentCiphertext,
} skipMultilineEncoding,
}); secretKeyTag,
return res.send({ approval: secretApprovalRequest }); secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
} }
} }
@@ -1097,7 +1130,9 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
params: { secretName } params: { secretName }
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req); } = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
const { membership } = await checkSecretsPermission({ logger.info(`deleteSecretByName: delete a encrypted secret by name [environment=${environment}] [workspaceId=${workspaceId}] [type=${type}]`)
await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
environment, environment,
@@ -1105,25 +1140,32 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Delete secretAction: ProjectPermissionActions.Delete
}); });
if (membership && type !== "personal") { if (req.authData.authPayload instanceof User) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); const membership = await Membership.findOne({
if (secretApprovalPolicy) { user: req.authData.authPayload._id,
const secretApprovalRequest = await generateSecretApprovalRequest({ workspace: new Types.ObjectId(workspaceId)
workspaceId, });
environment,
secretPath, if (membership && type !== "personal") {
authData: req.authData, const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
policy: secretApprovalPolicy, if (secretApprovalPolicy) {
commiterMembershipId: membership._id.toString(), const secretApprovalRequest = await generateSecretApprovalRequest({
data: { workspaceId,
[CommitType.DELETE]: [ environment,
{ secretPath,
secretName authData: req.authData,
} policy: secretApprovalPolicy,
] commiterMembershipId: membership._id.toString(),
} data: {
}); [CommitType.DELETE]: [
return res.send({ approval: secretApprovalRequest }); {
secretName
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
} }
} }
@@ -1155,7 +1197,9 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId } body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req); } = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
const { membership } = await checkSecretsPermission({ logger.info(`createSecretByNameBatch: create a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`)
await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
environment, environment,
@@ -1163,21 +1207,28 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Create secretAction: ProjectPermissionActions.Create
}); });
if (membership) { if (req.authData.authPayload instanceof User) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); const membership = await Membership.findOne({
if (secretApprovalPolicy) { user: req.authData.authPayload._id,
const secretApprovalRequest = await generateSecretApprovalRequest({ workspace: new Types.ObjectId(workspaceId)
workspaceId, });
environment,
secretPath, if (membership) {
authData: req.authData, const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
policy: secretApprovalPolicy, if (secretApprovalPolicy) {
commiterMembershipId: membership._id.toString(), const secretApprovalRequest = await generateSecretApprovalRequest({
data: { workspaceId,
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared") environment,
} secretPath,
}); authData: req.authData,
return res.send({ approval: secretApprovalRequest }); policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
}
} }
} }
@@ -1207,7 +1258,9 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId } body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req); } = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
const { membership } = await checkSecretsPermission({ logger.info(`updateSecretByNameBatch: update a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`)
await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
environment, environment,
@@ -1215,21 +1268,28 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Edit secretAction: ProjectPermissionActions.Edit
}); });
if (membership) { if (req.authData.authPayload instanceof User) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); const membership = await Membership.findOne({
if (secretApprovalPolicy) { user: req.authData.authPayload._id,
const secretApprovalRequest = await generateSecretApprovalRequest({ workspace: new Types.ObjectId(workspaceId)
workspaceId, });
environment,
secretPath, if (membership) {
policy: secretApprovalPolicy, const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
commiterMembershipId: membership._id.toString(), if (secretApprovalPolicy) {
data: { const secretApprovalRequest = await generateSecretApprovalRequest({
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared") workspaceId,
}, environment,
authData: req.authData secretPath,
}); policy: secretApprovalPolicy,
return res.send({ approval: secretApprovalRequest }); commiterMembershipId: membership._id.toString(),
data: {
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
}
} }
} }
@@ -1259,7 +1319,9 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId } body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req); } = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
const { membership } = await checkSecretsPermission({ logger.info(`deleteSecretByNameBatch: delete a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`)
await checkSecretsPermission({
authData: req.authData, authData: req.authData,
workspaceId, workspaceId,
environment, environment,
@@ -1267,21 +1329,28 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Delete secretAction: ProjectPermissionActions.Delete
}); });
if (membership) { if (req.authData.authPayload instanceof User) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); const membership = await Membership.findOne({
if (secretApprovalPolicy) { user: req.authData.authPayload._id,
const secretApprovalRequest = await generateSecretApprovalRequest({ workspace: new Types.ObjectId(workspaceId)
workspaceId, });
environment,
secretPath, if (membership) {
policy: secretApprovalPolicy, const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
commiterMembershipId: membership._id.toString(), if (secretApprovalPolicy) {
data: { const secretApprovalRequest = await generateSecretApprovalRequest({
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared") workspaceId,
}, environment,
authData: req.authData secretPath,
}); policy: secretApprovalPolicy,
return res.send({ approval: secretApprovalRequest }); commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
}
} }
} }
@@ -1,9 +1,9 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import { Secret, ServiceTokenDataV3 } from "../../models"; import { Membership, Secret, ServiceTokenDataV3, User } from "../../models";
import { SecretService } from "../../services"; import { SecretService } from "../../services";
import { getUserProjectPermissions } from "../../ee/services/ProjectRoleService"; import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
import { UnauthorizedRequestError } from "../../utils/errors"; import { UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/workspace"; import * as reqValidator from "../../validation/workspace";
@@ -19,9 +19,22 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response)
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req); } = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
if (membership.role !== "admin") authData: req.authData,
throw UnauthorizedRequestError({ message: "User must be an admin" }); workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
const secretsWithoutBlindIndex = await Secret.countDocuments({ const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
@@ -41,9 +54,22 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req); } = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
if (membership.role !== "admin") authData: req.authData,
throw UnauthorizedRequestError({ message: "User must be an admin" }); workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
const secrets = await Secret.find({ const secrets = await Secret.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
@@ -65,9 +91,22 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
body: { secretsToUpdate } body: { secretsToUpdate }
} = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req); } = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
if (membership.role !== "admin") authData: req.authData,
throw UnauthorizedRequestError({ message: "User must be an admin" }); workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
@@ -109,7 +148,7 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response)
const serviceTokenData = await ServiceTokenDataV3.find({ const serviceTokenData = await ServiceTokenDataV3.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}); }).populate("customRole");
return res.status(200).send({ return res.status(200).send({
serviceTokenData serviceTokenData
@@ -1,4 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, User } from "../../../models";
import { import {
CreateRoleSchema, CreateRoleSchema,
DeleteRoleSchema, DeleteRoleSchema,
@@ -11,7 +13,7 @@ import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
adminProjectPermissions, adminProjectPermissions,
getUserProjectPermissions, getAuthDataProjectPermissions,
memberProjectPermissions, memberProjectPermissions,
viewerProjectPermission viewerProjectPermission
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
@@ -39,7 +41,10 @@ export const createRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "user doesn't have the permission." }); throw BadRequestError({ message: "user doesn't have the permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." }); throw BadRequestError({ message: "User doesn't have the permission." });
} }
@@ -82,7 +87,11 @@ export const updateRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." }); throw BadRequestError({ message: "User doesn't have the org permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." }); throw BadRequestError({ message: "User doesn't have the workspace permission." });
} }
@@ -134,7 +143,11 @@ export const deleteRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." }); throw BadRequestError({ message: "User doesn't have the org permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, role.workspace.toString()); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: role.workspace
});
if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." }); throw BadRequestError({ message: "User doesn't have the workspace permission." });
} }
@@ -162,7 +175,11 @@ export const getRoles = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." }); throw BadRequestError({ message: "User doesn't have the org permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." }); throw BadRequestError({ message: "User doesn't have the workspace permission." });
} }
@@ -227,7 +244,19 @@ export const getUserWorkspacePermissions = async (req: Request, res: Response) =
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetUserProjectPermission, req); } = await validateRequest(GetUserProjectPermission, req);
const { permission, membership } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
let membership;
if (req.authData.authPayload instanceof User) {
membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
})
}
res.status(200).json({ res.status(200).json({
data: { data: {
@@ -1,10 +1,11 @@
import { Types } from "mongoose";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy"; import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy";
@@ -19,7 +20,11 @@ export const createSecretApprovalPolicy = async (req: Request, res: Response) =>
body: { approvals, secretPath, approvers, environment, workspaceId, name } body: { approvals, secretPath, approvers, environment, workspaceId, name }
} = await validateRequest(reqValidator.CreateSecretApprovalRule, req); } = await validateRequest(reqValidator.CreateSecretApprovalRule, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@@ -49,10 +54,11 @@ export const updateSecretApprovalPolicy = async (req: Request, res: Response) =>
const secretApproval = await SecretApprovalPolicy.findById(id); const secretApproval = await SecretApprovalPolicy.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND; if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
secretApproval.workspace.toString() workspaceId: secretApproval.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@@ -78,10 +84,11 @@ export const deleteSecretApprovalPolicy = async (req: Request, res: Response) =>
const secretApproval = await SecretApprovalPolicy.findById(id); const secretApproval = await SecretApprovalPolicy.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND; if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
secretApproval.workspace.toString() workspaceId: secretApproval.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@@ -99,7 +106,11 @@ export const getSecretApprovalPolicy = async (req: Request, res: Response) => {
query: { workspaceId } query: { workspaceId }
} = await validateRequest(reqValidator.GetSecretApprovalRuleList, req); } = await validateRequest(reqValidator.GetSecretApprovalRuleList, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@@ -117,7 +128,11 @@ export const getSecretApprovalPolicyOfBoard = async (req: Request, res: Response
query: { workspaceId, environment, secretPath } query: { workspaceId, environment, secretPath }
} = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req); } = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { secretPath, environment }) subject(ProjectPermissionSub.Secrets, { secretPath, environment })
@@ -1,7 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { getUserProjectPermissions } from "../../services/ProjectRoleService";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import { Folder } from "../../../models"; import { Folder, Membership, User } from "../../../models";
import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest"; import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest";
import * as reqValidator from "../../validation/secretApprovalRequest"; import * as reqValidator from "../../validation/secretApprovalRequest";
import { getFolderWithPathFromId } from "../../../services/FolderService"; import { getFolderWithPathFromId } from "../../../services/FolderService";
@@ -17,7 +16,15 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
query: { workspaceId } query: { workspaceId }
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req); } = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const approvalRequestCount = await SecretApprovalRequest.aggregate([ const approvalRequestCount = await SecretApprovalRequest.aggregate([
{ {
$match: { $match: {
@@ -65,7 +72,14 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
query: { status, committer, workspaceId, environment, limit, offset } query: { status, committer, workspaceId, environment, limit, offset }
} = await validateRequest(reqValidator.getSecretApprovalRequests, req); } = await validateRequest(reqValidator.getSecretApprovalRequests, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const query = { const query = {
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
@@ -148,10 +162,15 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
// allow to fetch only if its admin or is the committer or approver // allow to fetch only if its admin or is the committer or approver
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
@@ -190,10 +209,15 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id && secretApprovalRequest.committer !== membership.id &&
@@ -227,10 +251,15 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id && secretApprovalRequest.committer !== membership.id &&
@@ -272,10 +301,14 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
@@ -5,7 +5,7 @@ import { Folder, Secret } from "../../../models";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { BadRequestError } from "../../../utils/errors"; import { BadRequestError } from "../../../utils/errors";
import * as reqValidator from "../../../validation"; import * as reqValidator from "../../../validation";
@@ -74,7 +74,11 @@ export const getSecretVersions = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Failed to find secret" }); throw BadRequestError({ message: "Failed to find secret" });
} }
const { permission } = await getUserProjectPermissions(req.user._id, secret.workspace.toString()); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: secret.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@@ -157,10 +161,12 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
if (!toBeUpdatedSec) { if (!toBeUpdatedSec) {
throw BadRequestError({ message: "Failed to find secret" }); throw BadRequestError({ message: "Failed to find secret" });
} }
const { permission } = await getUserProjectPermissions(
req.user._id, const { permission } = await getAuthDataProjectPermissions({
toBeUpdatedSec.workspace.toString() authData: req.authData,
); workspaceId: toBeUpdatedSec.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@@ -1,11 +1,12 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/secretRotation"; import * as reqValidator from "../../validation/secretRotation";
import * as secretRotationService from "../../secretRotation/service"; import * as secretRotationService from "../../secretRotation/service";
import { import {
getUserProjectPermissions,
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@@ -23,7 +24,11 @@ export const createSecretRotation = async (req: Request, res: Response) => {
} }
} = await validateRequest(reqValidator.createSecretRotationV1, req); } = await validateRequest(reqValidator.createSecretRotationV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@@ -49,7 +54,12 @@ export const restartSecretRotations = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.restartSecretRotationV1, req); } = await validateRequest(reqValidator.restartSecretRotationV1, req);
const doc = await secretRotationService.getSecretRotationById({ id }); const doc = await secretRotationService.getSecretRotationById({ id });
const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: doc.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@@ -65,7 +75,12 @@ export const deleteSecretRotations = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.removeSecretRotationV1, req); } = await validateRequest(reqValidator.removeSecretRotationV1, req);
const doc = await secretRotationService.getSecretRotationById({ id }); const doc = await secretRotationService.getSecretRotationById({ id });
const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: doc.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@@ -80,7 +95,11 @@ export const getSecretRotations = async (req: Request, res: Response) => {
query: { workspaceId } query: { workspaceId }
} = await validateRequest(reqValidator.getSecretRotationV1, req); } = await validateRequest(reqValidator.getSecretRotationV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@@ -1,11 +1,12 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/secretRotationProvider"; import * as reqValidator from "../../validation/secretRotationProvider";
import * as secretRotationProviderService from "../../secretRotation/service"; import * as secretRotationProviderService from "../../secretRotation/service";
import { import {
getUserProjectPermissions,
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@@ -14,7 +15,11 @@ export const getProviderTemplates = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.getSecretRotationProvidersV1, req); } = await validateRequest(reqValidator.getSecretRotationProvidersV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@@ -4,7 +4,7 @@ import { validateRequest } from "../../../helpers/validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import * as reqValidator from "../../../validation/secretSnapshot"; import * as reqValidator from "../../../validation/secretSnapshot";
import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models"; import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models";
@@ -33,10 +33,11 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
secretSnapshot.workspace.toString() workspaceId: secretSnapshot.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@@ -27,7 +27,6 @@ import {
} from "../../models"; } from "../../models";
import { EESecretService } from "../../services"; import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
// import Folder, { TFolderSchema } from "../../../models/folder";
import { getFolderByPath, searchByFolderId } from "../../../services/FolderService"; import { getFolderByPath, searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
@@ -46,7 +45,7 @@ import {
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../../utils/errors"; import { BadRequestError } from "../../../utils/errors";
@@ -107,7 +106,11 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
query: { environment, directory, offset, limit } query: { environment, directory, offset, limit }
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req); } = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@@ -148,7 +151,11 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
query: { environment, directory } query: { environment, directory }
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req); } = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@@ -238,7 +245,11 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
body: { directory, environment, version } body: { directory, environment, version }
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req); } = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@@ -572,7 +583,11 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogsV1, req); } = await validateRequest(GetWorkspaceAuditLogsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs ProjectPermissionSub.AuditLogs
@@ -632,7 +647,11 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req); } = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs ProjectPermissionSub.AuditLogs
@@ -700,7 +719,11 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetWorkspaceTrustedIpsV1, req); } = await validateRequest(GetWorkspaceTrustedIpsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@@ -726,7 +749,11 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
body: { comment, isActive, ipAddress: ip } body: { comment, isActive, ipAddress: ip }
} = await validateRequest(AddWorkspaceTrustedIpV1, req); } = await validateRequest(AddWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@@ -792,7 +819,11 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
body: { ipAddress: ip, comment } body: { ipAddress: ip, comment }
} = await validateRequest(UpdateWorkspaceTrustedIpV1, req); } = await validateRequest(UpdateWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@@ -884,7 +915,11 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
params: { workspaceId, trustedIpId } params: { workspaceId, trustedIpId }
} = await validateRequest(DeleteWorkspaceTrustedIpV1, req); } = await validateRequest(DeleteWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@@ -8,13 +8,11 @@ import {
ServiceTokenDataV3Key, ServiceTokenDataV3Key,
Workspace Workspace
} from "../../../models"; } from "../../../models";
import { import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
IServiceTokenV3Scope,
IServiceTokenV3TrustedIp
} from "../../../models/serviceTokenDataV3";
import { import {
ActorType, ActorType,
EventType EventType,
Role
} from "../../models"; } from "../../models";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/serviceTokenDataV3"; import * as reqValidator from "../../../validation/serviceTokenDataV3";
@@ -22,14 +20,14 @@ import { createToken } from "../../../helpers/auth";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors"; import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
import { EEAuditLogService, EELicenseService } from "../../services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { getAuthSecret } from "../../../config"; import { getAuthSecret } from "../../../config";
import { AuthTokenType } from "../../../variables"; import { ADMIN, AuthTokenType, CUSTOM, MEMBER, VIEWER } from "../../../variables";
/** /**
* Return project key for service token V3 * Return project key for service token V3
@@ -163,7 +161,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
name, name,
workspaceId, workspaceId,
publicKey, publicKey,
scopes, role,
trustedIps, trustedIps,
expiresIn, expiresIn,
accessTokenTTL, accessTokenTTL,
@@ -172,7 +170,11 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
nonce, // for ServiceTokenDataV3Key nonce, // for ServiceTokenDataV3Key
} }
} = await validateRequest(reqValidator.CreateServiceTokenV3, req); } = await validateRequest(reqValidator.CreateServiceTokenV3, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens
@@ -181,6 +183,19 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const workspace = await Workspace.findById(workspaceId); const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw BadRequestError({ message: "Workspace not found" }); if (!workspace) throw BadRequestError({ message: "Workspace not found" });
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
let customRole;
if (isCustomRole) {
customRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: workspace._id
});
if (!customRole) throw BadRequestError({ message: "Role not found" });
}
const plan = await EELicenseService.getPlan(workspace.organization); const plan = await EELicenseService.getPlan(workspace.organization);
// validate trusted ips // validate trusted ips
@@ -219,7 +234,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
accessTokenUsageCount: 0, accessTokenUsageCount: 0,
tokenVersion: 1, tokenVersion: 1,
trustedIps: reformattedTrustedIps, trustedIps: reformattedTrustedIps,
scopes, role: isCustomRole ? CUSTOM : role,
customRole,
isActive, isActive,
expiresAt, expiresAt,
accessTokenTTL, accessTokenTTL,
@@ -250,7 +266,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
metadata: { metadata: {
name, name,
isActive, isActive,
scopes: scopes as Array<IServiceTokenV3Scope>, role,
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>, trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt expiresAt
} }
@@ -278,7 +294,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
body: { body: {
name, name,
isActive, isActive,
scopes, role,
trustedIps, trustedIps,
expiresIn, expiresIn,
accessTokenTTL, accessTokenTTL,
@@ -291,10 +307,10 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
message: "Service token not found" message: "Service token not found"
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
serviceTokenData.workspace.toString() workspaceId: serviceTokenData.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
@@ -304,6 +320,20 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
const workspace = await Workspace.findById(serviceTokenData.workspace); const workspace = await Workspace.findById(serviceTokenData.workspace);
if (!workspace) throw BadRequestError({ message: "Workspace not found" }); if (!workspace) throw BadRequestError({ message: "Workspace not found" });
let customRole;
if (role) {
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
if (isCustomRole) {
customRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: workspace._id
});
if (!customRole) throw BadRequestError({ message: "Role not found" });
}
}
const plan = await EELicenseService.getPlan(workspace.organization); const plan = await EELicenseService.getPlan(workspace.organization);
// validate trusted ips // validate trusted ips
@@ -335,7 +365,15 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
{ {
name, name,
isActive, isActive,
scopes, role: customRole ? CUSTOM : role,
...(customRole ? {
customRole
} : {}),
...(role && !customRole ? { // non-custom role
$unset: {
customRole: 1
}
} : {}),
trustedIps: reformattedTrustedIps, trustedIps: reformattedTrustedIps,
expiresAt, expiresAt,
accessTokenTTL, accessTokenTTL,
@@ -357,7 +395,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
metadata: { metadata: {
name: serviceTokenData.name, name: serviceTokenData.name,
isActive, isActive,
scopes: scopes as Array<IServiceTokenV3Scope>, role,
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>, trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt expiresAt
} }
@@ -388,10 +426,10 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
message: "Service token not found" message: "Service token not found"
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
serviceTokenData.workspace.toString() workspaceId: serviceTokenData.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
@@ -415,7 +453,7 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
metadata: { metadata: {
name: serviceTokenData.name, name: serviceTokenData.name,
isActive: serviceTokenData.isActive, isActive: serviceTokenData.isActive,
scopes: serviceTokenData.scopes as Array<IServiceTokenV3Scope>, role: serviceTokenData.role,
trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>, trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt: serviceTokenData.expiresAt expiresAt: serviceTokenData.expiresAt
} }
+1 -1
View File
@@ -2,7 +2,7 @@ export enum ActorType {
USER = "user", USER = "user",
SERVICE = "service", SERVICE = "service",
SERVICE_V3 = "service-v3", SERVICE_V3 = "service-v3",
Machine = "machine" // Machine = "machine"
} }
export enum UserAgentType { export enum UserAgentType {
+8 -8
View File
@@ -1,5 +1,5 @@
import { ActorType, EventType } from "./enums"; import { ActorType, EventType } from "./enums";
import { IServiceTokenV3Scope, IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3"; import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
interface UserActorMetadata { interface UserActorMetadata {
userId: string; userId: string;
@@ -26,11 +26,11 @@ export interface ServiceActorV3 {
metadata: ServiceActorMetadata; metadata: ServiceActorMetadata;
} }
export interface MachineActor { // export interface MachineActor {
type: ActorType.Machine; // type: ActorType.Machine;
} // }
export type Actor = UserActor | ServiceActor | ServiceActorV3 | MachineActor; export type Actor = UserActor | ServiceActor | ServiceActorV3;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;
@@ -225,7 +225,7 @@ interface CreateServiceTokenV3Event {
metadata: { metadata: {
name: string; name: string;
isActive: boolean; isActive: boolean;
scopes: Array<IServiceTokenV3Scope>; role: string;
trustedIps: Array<IServiceTokenV3TrustedIp>; trustedIps: Array<IServiceTokenV3TrustedIp>;
expiresAt?: Date; expiresAt?: Date;
}; };
@@ -236,7 +236,7 @@ interface UpdateServiceTokenV3Event {
metadata: { metadata: {
name?: string; name?: string;
isActive?: boolean; isActive?: boolean;
scopes?: Array<IServiceTokenV3Scope>; role?: string;
trustedIps?: Array<IServiceTokenV3TrustedIp>; trustedIps?: Array<IServiceTokenV3TrustedIp>;
expiresAt?: Date; expiresAt?: Date;
}; };
@@ -247,7 +247,7 @@ interface DeleteServiceTokenV3Event {
metadata: { metadata: {
name: string; name: string;
isActive: boolean; isActive: boolean;
scopes: Array<IServiceTokenV3Scope>; role: string;
expiresAt?: Date; expiresAt?: Date;
trustedIps: Array<IServiceTokenV3TrustedIp>; trustedIps: Array<IServiceTokenV3TrustedIp>;
}; };
+89 -27
View File
@@ -1,3 +1,4 @@
import { Types } from "mongoose";
import { import {
AbilityBuilder, AbilityBuilder,
ForcedSubject, ForcedSubject,
@@ -6,11 +7,14 @@ import {
buildMongoQueryMatcher, buildMongoQueryMatcher,
createMongoAbility createMongoAbility
} from "@casl/ability"; } from "@casl/ability";
import { Membership } from "../../models"; import { UnauthorizedRequestError } from "../../utils/errors";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js"; import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
import picomatch from "picomatch"; import picomatch from "picomatch";
import { AuthData } from "../../interfaces/middleware";
import { ActorType, IRole } from "../models";
import { Membership, ServiceTokenData, ServiceTokenDataV3 } from "../../models";
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
import { checkIPAgainstBlocklist } from "../../utils/ip";
const $glob: FieldInstruction<string> = { const $glob: FieldInstruction<string> = {
type: "field", type: "field",
@@ -239,31 +243,89 @@ const buildViewerPermission = () => {
export const viewerProjectPermission = buildViewerPermission(); export const viewerProjectPermission = buildViewerPermission();
export const getUserProjectPermissions = async (userId: string, workspaceId: string) => { /**
// TODO(akhilmhdh): speed this up by pulling from cache later * Return permissions for user/service pertaining to workspace with id [workspaceId]
const membership = await Membership.findOne({ *
user: userId, * Note: should not rely on this function for ST V2 authorization logic
workspace: workspaceId * b/c ST V2 does not support role-based access control
}) */
.populate<{ export const getAuthDataProjectPermissions = async ({
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] }; authData,
}>("customRole") workspaceId
.exec(); }: {
authData: AuthData;
workspaceId: Types.ObjectId;
}) => {
let role: "admin" | "member" | "viewer" | "custom";
let customRole;
switch (authData.actor.type) {
case ActorType.USER: {
const membership = await Membership.findOne({
user: authData.authPayload._id,
workspace: workspaceId
})
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError();
}
role = membership.role;
customRole = membership.customRole;
break;
}
case ActorType.SERVICE: {
const serviceTokenData = await ServiceTokenData.findById(authData.authPayload._id);
if (!serviceTokenData || !serviceTokenData.workspace.equals(workspaceId)) throw UnauthorizedRequestError();
role = "viewer";
break;
}
case ActorType.SERVICE_V3: {
const serviceTokenData = await ServiceTokenDataV3
.findById(authData.authPayload._id)
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!serviceTokenData || (serviceTokenData.role === "custom" && !serviceTokenData.customRole)) {
throw UnauthorizedRequestError();
}
if (!membership || (membership.role === "custom" && !membership.customRole)) { checkIPAgainstBlocklist({
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" }); ipAddress: authData.ipAddress,
trustedIps: serviceTokenData.trustedIps
});
role = serviceTokenData.role;
customRole = serviceTokenData.customRole;
break;
}
default:
throw UnauthorizedRequestError();
} }
if (membership.role === "admin") return { permission: adminProjectPermissions, membership }; switch (role) {
if (membership.role === "member") return { permission: memberProjectPermissions, membership }; case ADMIN:
if (membership.role === "viewer") return { permission: viewerProjectPermission, membership }; return { permission: adminProjectPermissions };
case MEMBER:
if (membership.role === "custom") { return { permission: memberProjectPermissions };
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions, { case VIEWER:
conditionsMatcher return { permission: viewerProjectPermission };
}); case CUSTOM: {
return { permission, membership }; if (!customRole) throw UnauthorizedRequestError();
return {
permission: createMongoAbility<ProjectPermissionSet>(
customRole.permissions,
{ conditionsMatcher }
)
};
}
default:
throw UnauthorizedRequestError();
} }
}
throw BadRequestError({ message: "User role not found" });
};
-38
View File
@@ -13,13 +13,11 @@ import {
Folder, Folder,
ISecret, ISecret,
IServiceTokenData, IServiceTokenData,
IServiceTokenDataV3,
Secret, Secret,
SecretBlindIndexData, SecretBlindIndexData,
ServiceTokenData, ServiceTokenData,
TFolderRootSchema TFolderRootSchema
} from "../models"; } from "../models";
import { Permission } from "../models/serviceTokenDataV3";
import { EventType, SecretVersion } from "../ee/models"; import { EventType, SecretVersion } from "../ee/models";
import { import {
BadRequestError, BadRequestError,
@@ -51,42 +49,6 @@ import picomatch from "picomatch";
import path from "path"; import path from "path";
import { getAnImportedSecret } from "../services/SecretImportService"; import { getAnImportedSecret } from "../services/SecretImportService";
/**
* Validate scope for service token v3
* @param authPayload
* @param environment
* @param secretPath
* @returns
*/
export const isValidScopeV3 = ({
authPayload,
environment,
secretPath,
requiredPermissions
}: {
authPayload: IServiceTokenDataV3;
environment: string;
secretPath: string;
requiredPermissions: Permission[];
}) => {
const { scopes } = authPayload;
const validScope = scopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (
validScope &&
!requiredPermissions.every((permission) => validScope.permissions.includes(permission))
) {
return false;
}
return Boolean(validScope);
};
/** /**
* Validate scope for service token v2 * Validate scope for service token v2
* @param authPayload * @param authPayload
+28 -2
View File
@@ -5,7 +5,7 @@ import express from "express";
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 { logger } from "./utils/logging"; import { initLogger, logger } from "./utils/logging";
import httpLogger from "pino-http"; import httpLogger from "pino-http";
import { DatabaseService } from "./services"; import { DatabaseService } from "./services";
import { EELicenseService, GithubSecretScanningService } from "./ee/services"; import { EELicenseService, GithubSecretScanningService } from "./ee/services";
@@ -35,6 +35,7 @@ import {
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3"; import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3"; import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
import { import {
admin as v1AdminRouter,
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
integrationAuth as v1IntegrationAuthRouter, integrationAuth as v1IntegrationAuthRouter,
@@ -81,6 +82,7 @@ import { healthCheck } from "./routes/status";
import { RouteNotFoundError } from "./utils/errors"; import { RouteNotFoundError } from "./utils/errors";
import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { requestErrorHandler } from "./middleware/requestErrorHandler";
import { import {
getMongoURL,
getNodeEnv, getNodeEnv,
getPort, getPort,
getSecretScanningGitAppId, getSecretScanningGitAppId,
@@ -94,12 +96,20 @@ import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecre
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
const SmeeClient = require("smee-client"); // eslint-disable-line const SmeeClient = require("smee-client"); // eslint-disable-line
import path from "path"; import path from "path";
import { serverConfigInit } from "./config/serverConfig";
import { initRedis } from "./services/RedisService";
let handler: null | any = null; let handler: null | any = null;
const main = async () => { const main = async () => {
await initLogger();
const port = await getPort(); const port = await getPort();
// initializing the database connection + redis
await initRedis()
await DatabaseService.initDatabase(await getMongoURL());
const serverCfg = await serverConfigInit();
await setup(); await setup();
await EELicenseService.initGlobalFeatureSet(); await EELicenseService.initGlobalFeatureSet();
@@ -203,6 +213,7 @@ const main = async () => {
// v1 routes // v1 routes
app.use("/api/v1/signup", v1SignupRouter); app.use("/api/v1/signup", v1SignupRouter);
app.use("/api/v1/auth", v1AuthRouter); app.use("/api/v1/auth", v1AuthRouter);
app.use("/api/v1/admin", v1AdminRouter);
app.use("/api/v1/bot", v1BotRouter); app.use("/api/v1/bot", v1BotRouter);
app.use("/api/v1/user", v1UserRouter); app.use("/api/v1/user", v1UserRouter);
app.use("/api/v1/user-action", v1UserActionRouter); app.use("/api/v1/user-action", v1UserActionRouter);
@@ -271,7 +282,22 @@ const main = async () => {
app.use(requestErrorHandler); app.use(requestErrorHandler);
const server = app.listen(port, async () => { const server = app.listen(port, async () => {
logger.info(`Server started listening at port ${port}`); if (!serverCfg.initialized) {
logger.info(`Welcome to Infisical
Create your Infisical administrator account at:
http://localhost:${port}/admin/signup
`);
} else {
logger.info(`Welcome back!
To access Infisical Administrator Panel open
http://localhost:${port}/admin
To access Infisical server
http://localhost:${port}
`);
}
}); });
// await createTestUserForDevelopment(); // await createTestUserForDevelopment();
+14 -10
View File
@@ -7,17 +7,21 @@ import requireSecretAuth from "./requireSecretAuth";
import requireSecretsAuth from "./requireSecretsAuth"; import requireSecretsAuth from "./requireSecretsAuth";
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled"; import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
import requireE2EEOff from "./requireE2EEOff"; import requireE2EEOff from "./requireE2EEOff";
import { requireSuperAdminAccess } from "./requireSuperAdminAccess";
import validateRequest from "./validateRequest"; import validateRequest from "./validateRequest";
import { disableSignUpByServerCfg } from "./serverAdmin";
export { export {
requireAuth, requireAuth,
requireMfaAuth, requireMfaAuth,
requireSignupAuth, requireSignupAuth,
requireWorkspaceAuth, requireWorkspaceAuth,
requireServiceTokenAuth, requireServiceTokenAuth,
requireSecretAuth, requireSecretAuth,
requireSecretsAuth, requireSecretsAuth,
requireBlindIndicesEnabled, requireBlindIndicesEnabled,
requireE2EEOff, requireE2EEOff,
validateRequest, validateRequest,
requireSuperAdminAccess,
disableSignUpByServerCfg
}; };
@@ -15,7 +15,7 @@ export const requestErrorHandler: ErrorRequestHandler = async (
if (res.headersSent) return next(); if (res.headersSent) return next();
let error: RequestError; let error: RequestError;
switch (true) { switch (true) {
case err instanceof TokenExpiredError: case err instanceof TokenExpiredError:
error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" }); error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" });
@@ -31,7 +31,7 @@ export const requestErrorHandler: ErrorRequestHandler = async (
break; break;
} }
logger[mapToPinoLogLevel(error.level)](error); logger[mapToPinoLogLevel(error.level)]({ msg: error });
if (req.user) { if (req.user) {
Sentry.setUser({ email: (req.user as any).email }); Sentry.setUser({ email: (req.user as any).email });
@@ -0,0 +1,8 @@
import { NextFunction, Request, Response } from "express";
import { UnauthorizedRequestError } from "../utils/errors";
export const requireSuperAdminAccess = (req: Request, _res: Response, next: NextFunction) => {
const isSuperAdmin = req.user.superAdmin;
if (!isSuperAdmin) throw UnauthorizedRequestError({ message: "Requires superadmin access" });
return next();
};
+9
View File
@@ -0,0 +1,9 @@
import { NextFunction, Request, Response } from "express";
import { getServerConfig } from "../config/serverConfig";
import { BadRequestError } from "../utils/errors";
export const disableSignUpByServerCfg = (_req: Request, _res: Response, next: NextFunction) => {
const cfg = getServerConfig();
if (!cfg.allowSignUp) throw BadRequestError({ message: "Signup are disabled" });
return next();
};
+25
View File
@@ -0,0 +1,25 @@
import { Schema, Types, model } from "mongoose";
export interface IServerConfig {
_id: Types.ObjectId;
initialized: boolean;
allowSignUp: boolean;
}
const serverConfigSchema = new Schema<IServerConfig>(
{
initialized: {
type: Boolean,
default: false
},
allowSignUp: {
type: Boolean,
default: true
}
},
{
timestamps: true
}
);
export const ServerConfig = model<IServerConfig>("ServerConfig", serverConfigSchema);
+10 -32
View File
@@ -1,16 +1,6 @@
import { Document, Schema, Types, model } from "mongoose"; import { Document, Schema, Types, model } from "mongoose";
import { IPType } from "../ee/models"; import { IPType } from "../ee/models";
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
export enum Permission {
READ = "read",
WRITE = "write"
}
export interface IServiceTokenV3Scope {
environment: string;
secretPath: string;
permissions: Permission[];
}
export interface IServiceTokenV3TrustedIp { export interface IServiceTokenV3TrustedIp {
ipAddress: string; ipAddress: string;
@@ -33,7 +23,8 @@ export interface IServiceTokenDataV3 extends Document {
isRefreshTokenRotationEnabled: boolean; isRefreshTokenRotationEnabled: boolean;
expiresAt?: Date; expiresAt?: Date;
accessTokenTTL: number; accessTokenTTL: number;
scopes: Array<IServiceTokenV3Scope>; role: "admin" | "member" | "viewer" | "custom";
customRole: Types.ObjectId;
trustedIps: Array<IServiceTokenV3TrustedIp>; trustedIps: Array<IServiceTokenV3TrustedIp>;
} }
@@ -100,28 +91,15 @@ const serviceTokenDataV3Schema = new Schema(
default: 7200, default: 7200,
required: true required: true
}, },
scopes: { role: {
type: [ type: String,
{ enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
environment: {
type: String,
required: true
},
secretPath: {
type: String,
default: "/",
required: true
},
permissions: {
type: [String],
enum: [Permission.READ, Permission.WRITE],
default: [Permission.READ],
required: true
}
}
],
required: true required: true
}, },
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
},
trustedIps: { trustedIps: {
type: [ type: [
{ {
+130 -114
View File
@@ -1,125 +1,141 @@
import { Document, Schema, Types, model } from "mongoose"; import { Document, Schema, Types, model } from "mongoose";
export enum AuthMethod { export enum AuthMethod {
EMAIL = "email", EMAIL = "email",
GOOGLE = "google", GOOGLE = "google",
GITHUB = "github", GITHUB = "github",
GITLAB = "gitlab", GITLAB = "gitlab",
OKTA_SAML = "okta-saml", OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml", AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml", JUMPCLOUD_SAML = "jumpcloud-saml"
} }
export interface IUser extends Document { export interface IUser extends Document {
_id: Types.ObjectId; _id: Types.ObjectId;
authProvider?: AuthMethod; authProvider?: AuthMethod;
authMethods: AuthMethod[]; authMethods: AuthMethod[];
email: string; email: string;
firstName?: string; superAdmin?: boolean;
lastName?: string; firstName?: string;
encryptionVersion: number; lastName?: string;
protectedKey: string; encryptionVersion: number;
protectedKeyIV: string; protectedKey: string;
protectedKeyTag: string; protectedKeyIV: string;
publicKey?: string; protectedKeyTag: string;
encryptedPrivateKey?: string; publicKey?: string;
iv?: string; encryptedPrivateKey?: string;
tag?: string; iv?: string;
salt?: string; tag?: string;
verifier?: string; salt?: string;
isMfaEnabled: boolean; verifier?: string;
mfaMethods: boolean; isMfaEnabled: boolean;
devices: { mfaMethods: boolean;
ip: string; devices: {
userAgent: string; ip: string;
}[]; userAgent: string;
}[];
} }
const userSchema = new Schema<IUser>( const userSchema = new Schema<IUser>(
{ {
authProvider: { // TODO field: deprecate authProvider: {
type: String, // TODO field: deprecate
enum: AuthMethod, type: String,
}, enum: AuthMethod
authMethods: { },
type: [{ authMethods: {
type: String, type: [
enum: AuthMethod, {
}], type: String,
default: [AuthMethod.EMAIL], enum: AuthMethod
required: true }
}, ],
email: { default: [AuthMethod.EMAIL],
type: String, required: true
required: true, },
unique: true, email: {
}, type: String,
firstName: { required: true,
type: String, unique: true
}, },
lastName: { firstName: {
type: String, type: String
}, },
encryptionVersion: { lastName: {
type: Number, type: String
select: false, },
default: 1, // to resolve backward-compatibility issues encryptionVersion: {
}, type: Number,
protectedKey: { // introduced as part of encryption version 2 select: false,
type: String, default: 1 // to resolve backward-compatibility issues
select: false, },
}, protectedKey: {
protectedKeyIV: { // introduced as part of encryption version 2 // introduced as part of encryption version 2
type: String, type: String,
select: false, select: false
}, },
protectedKeyTag: { // introduced as part of encryption version 2 protectedKeyIV: {
type: String, // introduced as part of encryption version 2
select: false, type: String,
}, select: false
publicKey: { },
type: String, protectedKeyTag: {
select: false, // introduced as part of encryption version 2
}, type: String,
encryptedPrivateKey: { select: false
type: String, },
select: false, publicKey: {
}, type: String,
iv: { // iv of [encryptedPrivateKey] select: false
type: String, },
select: false, encryptedPrivateKey: {
}, type: String,
tag: { // tag of [encryptedPrivateKey] select: false
type: String, },
select: false, superAdmin: {
}, type: Boolean
salt: { },
type: String, iv: {
select: false, // iv of [encryptedPrivateKey]
}, type: String,
verifier: { select: false
type: String, },
select: false, tag: {
}, // tag of [encryptedPrivateKey]
isMfaEnabled: { type: String,
type: Boolean, select: false
default: false, },
}, salt: {
mfaMethods: [{ type: String,
type: String, select: false
}], },
devices: { verifier: {
type: [{ type: String,
ip: String, select: false
userAgent: String, },
}], isMfaEnabled: {
default: [], type: Boolean,
select: false, default: false
}, },
}, mfaMethods: [
{ {
timestamps: true, type: String
} }
],
devices: {
type: [
{
ip: String,
userAgent: String
}
],
default: [],
select: false
}
},
{
timestamps: true
}
); );
export const User = model<IUser>("User", userSchema); export const User = model<IUser>("User", userSchema);
+20
View File
@@ -0,0 +1,20 @@
import express from "express";
import { adminController } from "../../controllers/v1";
const router = express.Router();
import { requireAuth, requireSuperAdminAccess } from "../../middleware";
import { AuthMode } from "../../variables";
router.get("/config", adminController.getServerConfigInfo);
router.post("/signup", adminController.adminSignUp);
router.patch(
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireSuperAdminAccess,
adminController.updateServerConfig
);
export default router;
+3 -1
View File
@@ -18,6 +18,7 @@ import integrationAuth from "./integrationAuth";
import secretsFolder from "./secretsFolder"; import secretsFolder from "./secretsFolder";
import webhooks from "./webhook"; import webhooks from "./webhook";
import secretImps from "./secretImps"; import secretImps from "./secretImps";
import admin from "./admin";
export { export {
signup, signup,
@@ -39,5 +40,6 @@ export {
secretsFolder, secretsFolder,
webhooks, webhooks,
secretImps, secretImps,
sso sso,
admin
}; };
+3
View File
@@ -2,18 +2,21 @@ import express from "express";
const router = express.Router(); const router = express.Router();
import { signupController } from "../../controllers/v1"; import { signupController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
import { disableSignUpByServerCfg } from "../../middleware";
// TODO: consider moving to users/v3/signup // TODO: consider moving to users/v3/signup
router.post( router.post(
// TODO endpoint: consider moving to v3/users/signup/mail // TODO endpoint: consider moving to v3/users/signup/mail
"/email/signup", "/email/signup",
disableSignUpByServerCfg,
authLimiter, authLimiter,
signupController.beginEmailSignup signupController.beginEmailSignup
); );
router.post( router.post(
"/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify "/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify
disableSignUpByServerCfg,
authLimiter, authLimiter,
signupController.verifyEmailSignup signupController.verifyEmailSignup
); );
-8
View File
@@ -36,12 +36,4 @@ router.delete(
environmentController.deleteWorkspaceEnvironment environmentController.deleteWorkspaceEnvironment
); );
router.get(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
environmentController.getAllAccessibleEnvironmentsOfWorkspace
);
export default router; export default router;
+39 -37
View File
@@ -1,49 +1,51 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { body } from "express-validator"; import { body } from "express-validator";
import { requireSignupAuth, validateRequest } from "../../middleware"; import { disableSignUpByServerCfg, requireSignupAuth, validateRequest } from "../../middleware";
import { signupController } from "../../controllers/v2"; import { signupController } from "../../controllers/v2";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
router.post( router.post(
"/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup) "/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup),
authLimiter, disableSignUpByServerCfg,
requireSignupAuth, authLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(), requireSignupAuth,
body("firstName").exists().isString().trim().notEmpty(), body("email").exists().isString().trim().notEmpty().isEmail(),
body("lastName").exists().isString().trim().notEmpty(), body("firstName").exists().isString().trim().notEmpty(),
body("protectedKey").exists().isString().trim().notEmpty(), body("lastName").exists().isString().trim().notEmpty(),
body("protectedKeyIV").exists().isString().trim().notEmpty(), body("protectedKey").exists().isString().trim().notEmpty(),
body("protectedKeyTag").exists().isString().trim().notEmpty(), body("protectedKeyIV").exists().isString().trim().notEmpty(),
body("publicKey").exists().isString().trim().notEmpty(), body("protectedKeyTag").exists().isString().trim().notEmpty(),
body("encryptedPrivateKey").exists().isString().trim().notEmpty(), body("publicKey").exists().isString().trim().notEmpty(),
body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), body("encryptedPrivateKey").exists().isString().trim().notEmpty(),
body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(),
body("salt").exists().isString().trim().notEmpty(), body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(),
body("verifier").exists().isString().trim().notEmpty(), body("salt").exists().isString().trim().notEmpty(),
body("organizationName").exists().isString().trim().notEmpty(), body("verifier").exists().isString().trim().notEmpty(),
validateRequest, body("organizationName").exists().isString().trim().notEmpty(),
signupController.completeAccountSignup validateRequest,
signupController.completeAccountSignup
); );
router.post( router.post(
"/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite "/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite
authLimiter, disableSignUpByServerCfg,
requireSignupAuth, authLimiter,
body("email").exists().isString().trim().notEmpty().isEmail(), requireSignupAuth,
body("firstName").exists().isString().trim().notEmpty(), body("email").exists().isString().trim().notEmpty().isEmail(),
body("lastName").exists().isString().trim().notEmpty(), body("firstName").exists().isString().trim().notEmpty(),
body("protectedKey").exists().isString().trim().notEmpty(), body("lastName").exists().isString().trim().notEmpty(),
body("protectedKeyIV").exists().isString().trim().notEmpty(), body("protectedKey").exists().isString().trim().notEmpty(),
body("protectedKeyTag").exists().isString().trim().notEmpty(), body("protectedKeyIV").exists().isString().trim().notEmpty(),
body("publicKey").exists().trim().notEmpty(), body("protectedKeyTag").exists().isString().trim().notEmpty(),
body("encryptedPrivateKey").exists().isString().trim().notEmpty(), body("publicKey").exists().trim().notEmpty(),
body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), body("encryptedPrivateKey").exists().isString().trim().notEmpty(),
body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(),
body("salt").exists().isString().trim().notEmpty(), body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(),
body("verifier").exists().isString().trim().notEmpty(), body("salt").exists().isString().trim().notEmpty(),
validateRequest, body("verifier").exists().isString().trim().notEmpty(),
signupController.completeAccountInvite validateRequest,
signupController.completeAccountInvite
); );
export default router; export default router;
+2 -1
View File
@@ -2,10 +2,11 @@ import express from "express";
const router = express.Router(); const router = express.Router();
import { signupController } from "../../controllers/v3"; import { signupController } from "../../controllers/v3";
import { authLimiter } from "../../helpers/rateLimiter"; import { authLimiter } from "../../helpers/rateLimiter";
import { validateRequest } from "../../middleware"; import { disableSignUpByServerCfg, validateRequest } from "../../middleware";
router.post( router.post(
"/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup "/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup
disableSignUpByServerCfg,
authLimiter, authLimiter,
validateRequest, validateRequest,
signupController.completeAccountSignup signupController.completeAccountSignup
+8 -5
View File
@@ -3,11 +3,14 @@ import { logger } from "../utils/logging";
let redisClient: TRedis | null; let redisClient: TRedis | null;
if (process.env.REDIS_URL) { export const initRedis = async () => {
redisClient = new Redis(process.env.REDIS_URL as string); if (process.env.REDIS_URL) {
} else { redisClient = new Redis(process.env.REDIS_URL as string);
logger.warn("Redis URL not set, skipping Redis initialization."); } else {
redisClient = null; logger.warn("Redis URL not set, skipping Redis initialization.");
redisClient = null;
}
} }
export { redisClient }; export { redisClient };
+15 -31
View File
@@ -8,30 +8,28 @@ import {
SMTP_HOST_ZOHOMAIL SMTP_HOST_ZOHOMAIL
} from "../variables"; } from "../variables";
import SMTPConnection from "nodemailer/lib/smtp-connection"; import SMTPConnection from "nodemailer/lib/smtp-connection";
import * as Sentry from "@sentry/node";
import { import {
getSmtpHost, getSmtpHost,
getSmtpPassword, getSmtpPassword,
getSmtpPort, getSmtpPort,
getSmtpSecure, getSmtpSecure,
getSmtpUsername, getSmtpUsername
} from "../config"; } from "../config";
import { logger } from "../utils/logging";
export const initSmtp = async () => { export const initSmtp = async () => {
const mailOpts: SMTPConnection.Options = { const mailOpts: SMTPConnection.Options = {
host: await getSmtpHost(), host: await getSmtpHost(),
port: await getSmtpPort(), port: await getSmtpPort()
}; };
if ((await getSmtpUsername()) && (await getSmtpPassword())) { if ((await getSmtpUsername()) && (await getSmtpPassword())) {
mailOpts.auth = { mailOpts.auth = {
user: await getSmtpUsername(), user: await getSmtpUsername(),
pass: await getSmtpPassword(), pass: await getSmtpPassword()
}; };
} }
if ((await getSmtpSecure()) ? (await getSmtpSecure()) : false) { if ((await getSmtpSecure()) ? await getSmtpSecure() : false) {
switch (await getSmtpHost()) { switch (await getSmtpHost()) {
case SMTP_HOST_SENDGRID: case SMTP_HOST_SENDGRID:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
@@ -39,38 +37,38 @@ export const initSmtp = async () => {
case SMTP_HOST_MAILGUN: case SMTP_HOST_MAILGUN:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
mailOpts.tls = { mailOpts.tls = {
ciphers: "TLSv1.2", ciphers: "TLSv1.2"
} };
break; break;
case SMTP_HOST_SOCKETLABS: case SMTP_HOST_SOCKETLABS:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
mailOpts.tls = { mailOpts.tls = {
ciphers: "TLSv1.2", ciphers: "TLSv1.2"
} };
break; break;
case SMTP_HOST_ZOHOMAIL: case SMTP_HOST_ZOHOMAIL:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
mailOpts.tls = { mailOpts.tls = {
ciphers: "TLSv1.2", ciphers: "TLSv1.2"
} };
break; break;
case SMTP_HOST_GMAIL: case SMTP_HOST_GMAIL:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
mailOpts.tls = { mailOpts.tls = {
ciphers: "TLSv1.2", ciphers: "TLSv1.2"
} };
break; break;
case SMTP_HOST_OFFICE365: case SMTP_HOST_OFFICE365:
mailOpts.requireTLS = true; mailOpts.requireTLS = true;
mailOpts.tls = { mailOpts.tls = {
ciphers: "TLSv1.2" ciphers: "TLSv1.2"
} };
break; break;
default: default:
if ((await getSmtpHost()).includes("amazonaws.com")) { if ((await getSmtpHost()).includes("amazonaws.com")) {
mailOpts.tls = { mailOpts.tls = {
ciphers: "TLSv1.2", ciphers: "TLSv1.2"
} };
} else { } else {
mailOpts.secure = true; mailOpts.secure = true;
} }
@@ -79,20 +77,6 @@ export const initSmtp = async () => {
} }
const transporter = nodemailer.createTransport(mailOpts); const transporter = nodemailer.createTransport(mailOpts);
transporter
.verify()
.then(async () => {
Sentry.setUser(null);
Sentry.captureMessage("SMTP - Successfully connected");
logger.info("SMTP - Successfully connected");
})
.catch(async (err) => {
Sentry.setUser(null);
Sentry.captureException(
`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}`
);
logger.error(err, `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`);
});
return transporter; return transporter;
}; };
+50 -47
View File
@@ -1,62 +1,65 @@
import { import { AuthMethod, User } from "../../../models";
AuthMethod,
User
} from "../../../models";
import { createToken } from "../../../helpers/auth"; import { createToken } from "../../../helpers/auth";
import { AuthTokenType } from "../../../variables"; import { AuthTokenType } from "../../../variables";
import { getAuthSecret, getJwtProviderAuthLifetime} from "../../../config"; import { getAuthSecret, getJwtProviderAuthLifetime } from "../../../config";
import { getServerConfig } from "../../../config/serverConfig";
interface SSOUserTokenFlowParams { interface SSOUserTokenFlowParams {
email: string; email: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
authMethod: AuthMethod; authMethod: AuthMethod;
callbackPort?: string; callbackPort?: string;
} }
export const handleSSOUserTokenFlow = async ({ export const handleSSOUserTokenFlow = async ({
email, email,
firstName, firstName,
lastName, lastName,
authMethod, authMethod,
callbackPort callbackPort
}: SSOUserTokenFlowParams) => { }: SSOUserTokenFlowParams) => {
let user = await User.findOne({ let user = await User.findOne({
email email
}).select("+publicKey"); }).select("+publicKey");
if (!user) {
user = await new User({
email,
authMethods: [authMethod],
firstName,
lastName
}).save();
}
let isLinkingRequired = false; const serverCfg = getServerConfig();
if (!user.authMethods.includes(authMethod)) { if (!user && !serverCfg.allowSignUp) throw new Error("User signup disabled");
if (!user) {
user = await new User({
email,
authMethods: [authMethod],
firstName,
lastName
}).save();
}
let isLinkingRequired = false;
if (!user.authMethods.includes(authMethod)) {
isLinkingRequired = true; isLinkingRequired = true;
} }
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
const providerAuthToken = createToken({ const providerAuthToken = createToken({
payload: { payload: {
authTokenType: AuthTokenType.PROVIDER_TOKEN, authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user._id.toString(), userId: user._id.toString(),
email: user.email, email: user.email,
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
authMethod, authMethod,
isUserCompleted, isUserCompleted,
isLinkingRequired, isLinkingRequired,
...(callbackPort ? { ...(callbackPort
? {
callbackPort callbackPort
} : {}) }
: {})
}, },
expiresIn: await getJwtProviderAuthLifetime(), expiresIn: await getJwtProviderAuthLifetime(),
secret: await getAuthSecret(), secret: await getAuthSecret()
}); });
return { isUserCompleted, providerAuthToken }; return { isUserCompleted, providerAuthToken };
} };
+1 -1
View File
@@ -1 +1 @@
export { logger } from "./logger"; export { logger, initLogger } from "./logger";
+64 -10
View File
@@ -1,15 +1,69 @@
import pino from "pino"; import pino, { Logger } from "pino";
import { getAwsCloudWatchLog, getNodeEnv } from "../../config";
export const logger = pino({ export let logger: Logger;
level: process.env.PINO_LOG_LEVEL || "trace",
timestamp: pino.stdTimeFunctions.isoTime, // https://github.com/pinojs/pino/blob/master/lib/levels.js#L13-L20
formatters: { const logLevelToSeverityLookup: Record<string, string> = {
bindings: (bindings) => { '10': 'TRACE',
return { '20': 'DEBUG',
'30': 'INFO',
'40': 'WARNING',
'50': 'ERROR',
'60': 'CRITICAL'
}
export const initLogger = async () => {
const awsCloudWatchLogCfg = await getAwsCloudWatchLog();
const nodeEnv = await getNodeEnv();
const isProduction = nodeEnv === "production";
const targets: pino.TransportMultiOptions["targets"][number][] = [
isProduction
? { level: "info", target: "pino/file", options: {} }
: {
level: "info",
target: "pino-pretty", // must be installed separately
options: {
colorize: true
}
}
];
if (awsCloudWatchLogCfg) {
targets.push({
target: "@serdnam/pino-cloudwatch-transport",
level: "info",
options: {
logGroupName: awsCloudWatchLogCfg.logGroupName,
logStreamName: awsCloudWatchLogCfg.logGroupName,
awsRegion: awsCloudWatchLogCfg.region,
awsAccessKeyId: awsCloudWatchLogCfg.accessKeyId,
awsSecretAccessKey: awsCloudWatchLogCfg.accessKeySecret,
interval: awsCloudWatchLogCfg.interval
}
});
}
const transport = pino.transport({
targets
});
logger = pino(
{
mixin(_context, level) {
return { 'severity': logLevelToSeverityLookup[level] || logLevelToSeverityLookup['30'] }
},
level: process.env.PINO_LOG_LEVEL || "info",
formatters: {
bindings: (bindings) => {
return {
pid: bindings.pid, pid: bindings.pid,
hostname: bindings.hostname hostname: bindings.hostname
// node_version: process.version // node_version: process.version
}; };
}
}
}, },
} transport
}); );
};
+60 -22
View File
@@ -2,12 +2,12 @@ import crypto from "crypto";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
import { EESecretService } from "../../ee/services"; import { EESecretService } from "../../ee/services";
import { redisClient } from "../../services/RedisService" import { redisClient } from "../../services/RedisService";
import { import {
IPType, IPType,
ISecretVersion, ISecretVersion,
Role, Role,
SecretSnapshot, SecretSnapshot,
SecretVersion, SecretVersion,
TrustedIP TrustedIP
} from "../../ee/models"; } from "../../ee/models";
@@ -30,7 +30,7 @@ import {
Workspace Workspace
} from "../../models"; } from "../../models";
import { generateKeyPair } from "../../utils/crypto"; import { generateKeyPair } from "../../utils/crypto";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; import { client, getEncryptionKey, getIsInfisicalCloud, getRootEncryptionKey } from "../../config";
import { import {
ADMIN, ADMIN,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
@@ -47,6 +47,7 @@ import {
memberProjectPermissions memberProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { logger } from "../logging"; import { logger } from "../logging";
import { getServerConfig, updateServerConfig } from "../../config/serverConfig";
/** /**
* Backfill secrets to ensure that they're all versioned and have * Backfill secrets to ensure that they're all versioned and have
@@ -693,7 +694,7 @@ export const backfillUserAuthMethods = async () => {
export const backfillPermission = async () => { export const backfillPermission = async () => {
const lockKey = "backfill_permission_lock"; const lockKey = "backfill_permission_lock";
const timeout = 900000; // 15 min lock timeout in milliseconds const timeout = 900000; // 15 min lock timeout in milliseconds
const lock = await redisClient?.set(lockKey, 1, "PX", timeout, "NX"); const lock = await redisClient?.set(lockKey, 1, "PX", timeout, "NX");
if (lock) { if (lock) {
@@ -705,13 +706,16 @@ export const backfillPermission = async () => {
$exists: true, $exists: true,
$ne: [] $ne: []
}, },
role: MEMBER, role: MEMBER
}) })
.populate<{ workspace: IWorkspace }>("workspace") .populate<{ workspace: IWorkspace }>("workspace")
.lean(); .lean();
// group memberships that need the same permission set // group memberships that need the same permission set
const roleMap = new Map<string, { membershipIds: string[], permissions: any[], organizationId: string, workspaceId: string }>(); const roleMap = new Map<
string,
{ membershipIds: string[]; permissions: any[]; organizationId: string; workspaceId: string }
>();
for (const membership of memberships) { for (const membership of memberships) {
// get permissions of members except secret permission // get permissions of members except secret permission
@@ -729,11 +733,11 @@ export const backfillPermission = async () => {
}); });
// environments that are not listed in deniedPermissions should be set to allowed for both read & and write // environments that are not listed in deniedPermissions should be set to allowed for both read & and write
membership.workspace.environments.forEach(env => { membership.workspace.environments.forEach((env) => {
if (!secretAccessRule?.[env.slug]) { if (!secretAccessRule?.[env.slug]) {
secretAccessRule[env.slug] = { read: true, write: true }; secretAccessRule[env.slug] = { read: true, write: true };
} }
}) });
const secretPermissions: any = []; const secretPermissions: any = [];
Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => { Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => {
@@ -769,21 +773,27 @@ export const backfillPermission = async () => {
const value = roleMap.get(key); const value = roleMap.get(key);
if (value) { if (value) {
value.membershipIds.push(membership._id.toString()); value.membershipIds.push(membership._id.toString());
value.organizationId = membership.workspace.organization.toString() value.organizationId = membership.workspace.organization.toString();
value.workspaceId = membership.workspace._id.toString() value.workspaceId = membership.workspace._id.toString();
} else { } else {
roleMap.set(key, { membershipIds: [membership._id.toString()], permissions: [...customPermissions, ...secretPermissions], organizationId: membership.workspace.organization.toString(), workspaceId: membership.workspace._id.toString() }); roleMap.set(key, {
membershipIds: [membership._id.toString()],
permissions: [...customPermissions, ...secretPermissions],
organizationId: membership.workspace.organization.toString(),
workspaceId: membership.workspace._id.toString()
});
} }
} }
for (const [key, value] of roleMap.entries()) { for (const [key, value] of roleMap.entries()) {
const { membershipIds, permissions, workspaceId, organizationId } = value const { membershipIds, permissions, workspaceId, organizationId } = value;
const membership_identity = crypto.randomBytes(3).toString("hex") const membership_identity = crypto.randomBytes(3).toString("hex");
const role = new Role({ const role = new Role({
name: `Limited [${membership_identity.toUpperCase()}]`, name: `Limited [${membership_identity.toUpperCase()}]`,
organization: organizationId, organization: organizationId,
workspace: workspaceId, workspace: workspaceId,
description: "This role was auto generated by Infisical in effort to migrate your project members to our new permission system", description:
"This role was auto generated by Infisical in effort to migrate your project members to our new permission system",
isOrgRole: false, isOrgRole: false,
slug: `custom-role-${membership_identity}`, slug: `custom-role-${membership_identity}`,
permissions: permissions permissions: permissions
@@ -792,7 +802,8 @@ export const backfillPermission = async () => {
await role.save(); await role.save();
for (const id of membershipIds) { for (const id of membershipIds) {
await Membership.findByIdAndUpdate(id, { // document db doesn't support update many so we must loop await Membership.findByIdAndUpdate(id, {
// document db doesn't support update many so we must loop
$set: { $set: {
role: CUSTOM, role: CUSTOM,
customRole: role customRole: role
@@ -815,11 +826,9 @@ export const backfillPermission = async () => {
); );
logger.info("Backfill: Finished converting owner role to member"); logger.info("Backfill: Finished converting owner role to member");
} catch (error) { } catch (error) {
logger.error(error, "An error occurred when running script [backfillPermission]"); logger.error(error, "An error occurred when running script [backfillPermission]");
} }
} else { } else {
logger.info("Could not acquire lock for script [backfillPermission], skipping"); logger.info("Could not acquire lock for script [backfillPermission], skipping");
} }
@@ -838,4 +847,33 @@ export const migrateRoleFromOwnerToAdmin = async () => {
); );
logger.info("Backfill: Finished converting owner role to member"); logger.info("Backfill: Finished converting owner role to member");
} };
export const migrationAssignSuperadmin = async () => {
const users = await User.find({}).sort({ createdAt: 1 }).limit(2);
const serverCfg = getServerConfig();
if (serverCfg.initialized) return;
if (await getIsInfisicalCloud()) {
await updateServerConfig({ initialized: true });
logger.info("Backfill: Infisical Cloud(initialized)");
return;
}
if (users.length) {
let superAdminUserId = "";
const firstAccount = users?.[0];
if (firstAccount.email === "[email protected]" && users.length === 2) {
superAdminUserId = users?.[1]?._id.toString();
} else {
superAdminUserId = firstAccount._id.toString();
}
if (superAdminUserId) {
const user = await User.findByIdAndUpdate(superAdminUserId, { superAdmin: true });
await updateServerConfig({ initialized: true });
logger.info(`Migrated ${user?.email} to superuser`);
}
logger.info("Backfill: Migrated first infisical user to super admin");
}
};
+14 -10
View File
@@ -1,9 +1,8 @@
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import { DatabaseService, TelemetryService } from "../../services"; import { TelemetryService } from "../../services";
import { setTransporter } from "../../helpers/nodemailer"; import { setTransporter } from "../../helpers/nodemailer";
import { EELicenseService } from "../../ee/services"; import { EELicenseService } from "../../ee/services";
import { initSmtp } from "../../services/smtp"; import { initSmtp } from "../../services/smtp";
import { createTestUserForDevelopment } from "../addDevelopmentUser";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
import { validateEncryptionKeysConfig } from "./validateConfig"; import { validateEncryptionKeysConfig } from "./validateConfig";
import { import {
@@ -18,14 +17,15 @@ import {
backfillServiceTokenMultiScope, backfillServiceTokenMultiScope,
backfillTrustedIps, backfillTrustedIps,
backfillUserAuthMethods, backfillUserAuthMethods,
migrateRoleFromOwnerToAdmin migrateRoleFromOwnerToAdmin,
migrationAssignSuperadmin
} from "./backfillData"; } from "./backfillData";
import { import {
reencryptBotOrgKeys, reencryptBotOrgKeys,
reencryptBotPrivateKeys, reencryptBotPrivateKeys,
reencryptSecretBlindIndexDataSalts reencryptSecretBlindIndexDataSalts
} from "./reencryptData"; } from "./reencryptData";
import { getMongoURL, getNodeEnv, getRedisUrl, getSentryDSN } from "../../config"; import { getNodeEnv, getRedisUrl, getSentryDSN } from "../../config";
import { import {
initializeGitHubStrategy, initializeGitHubStrategy,
initializeGitLabStrategy, initializeGitLabStrategy,
@@ -33,6 +33,7 @@ import {
initializeSamlStrategy initializeSamlStrategy
} from "../authn/passport"; } from "../authn/passport";
import { logger } from "../logging"; import { logger } from "../logging";
import { bootstrap } from "../../bootstrap";
/** /**
* Prepare Infisical upon startup. This includes tasks like: * Prepare Infisical upon startup. This includes tasks like:
@@ -55,14 +56,15 @@ export const setup = async () => {
await TelemetryService.logTelemetryMessage(); await TelemetryService.logTelemetryMessage();
// initializing SMTP configuration // initializing SMTP configuration
setTransporter(await initSmtp()); const transporter = await initSmtp();
setTransporter(transporter);
// initializing global feature set // initializing global feature set
await EELicenseService.initGlobalFeatureSet(); await EELicenseService.initGlobalFeatureSet();
// initializing auth strategies // initializing auth strategies
await initializeGoogleStrategy(); await initializeGoogleStrategy();
await initializeGitHubStrategy() await initializeGitHubStrategy();
await initializeGitLabStrategy(); await initializeGitLabStrategy();
await initializeSamlStrategy(); await initializeSamlStrategy();
@@ -71,8 +73,7 @@ export const setup = async () => {
// await reencryptBotPrivateKeys(); // await reencryptBotPrivateKeys();
// await reencryptSecretBlindIndexDataSalts(); // await reencryptSecretBlindIndexDataSalts();
// initializing the database connection await bootstrap({ transporter });
await DatabaseService.initDatabase(await getMongoURL());
/** /**
* NOTE: the order in this setup function is critical. * NOTE: the order in this setup function is critical.
@@ -92,7 +93,8 @@ export const setup = async () => {
await backfillTrustedIps(); await backfillTrustedIps();
await backfillUserAuthMethods(); await backfillUserAuthMethods();
// await backfillPermission(); // await backfillPermission();
await migrateRoleFromOwnerToAdmin() await migrateRoleFromOwnerToAdmin();
await migrationAssignSuperadmin();
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
// to base64 256-bit ROOT_ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY
@@ -108,5 +110,7 @@ export const setup = async () => {
environment: await getNodeEnv() environment: await getNodeEnv()
}); });
await createTestUserForDevelopment(); // akhilmhdh: removed dev account as we have now admin account onboarding flow
// That will be user's first account going forward
// await createTestUserForDevelopment();
}; };
+24
View File
@@ -0,0 +1,24 @@
import { z } from "zod";
export const UpdateServerConfigV1 = z.object({
body: z.object({
allowSignUp: z.boolean().optional()
})
});
export const SignupV1 = z.object({
body: z.object({
email: z.string().email().trim(),
firstName: z.string().trim(),
lastName: z.string().trim().optional(),
protectedKey: z.string().trim(),
protectedKeyIV: z.string().trim(),
protectedKeyTag: z.string().trim(),
publicKey: z.string().trim(),
encryptedPrivateKey: z.string().trim(),
encryptedPrivateKeyIV: z.string().trim(),
encryptedPrivateKeyTag: z.string().trim(),
salt: z.string().trim(),
verifier: z.string().trim()
})
});
+6 -79
View File
@@ -1,64 +1,5 @@
import { Types } from "mongoose";
import { IServiceTokenDataV3 } from "../models";
import { Permission } from "../models/serviceTokenDataV3";
import { z } from "zod"; import { z } from "zod";
import { UnauthorizedRequestError } from "../utils/errors"; import { MEMBER } from "../variables";
import { isValidScopeV3 } from "../helpers";
import { AuthData } from "../interfaces/middleware";
import { checkIPAgainstBlocklist } from "../utils/ip";
/**
* Validate that service token (client) can access workspace
* with id [workspaceId] and its environment [environment] with required permissions
* [requiredPermissions]
* @param {Object} obj
* @param {ServiceTokenData} obj.serviceTokenData - service token client
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
* @param {String} environment - (optional) environment in workspace to validate against
* @param {String[]} acceptedPermissions - accepted permissions as part of the endpoint
*/
export const validateServiceTokenDataV3ClientForWorkspace = async ({
authData,
serviceTokenData,
workspaceId,
environment,
secretPath = "/",
requiredPermissions
}: {
authData: AuthData;
serviceTokenData: IServiceTokenDataV3;
workspaceId: Types.ObjectId;
environment?: string;
secretPath?: string;
requiredPermissions: Permission[];
}) => {
// validate ST V3 IP address
checkIPAgainstBlocklist({
ipAddress: authData.ipAddress,
trustedIps: serviceTokenData.trustedIps
});
if (!serviceTokenData.workspace.equals(workspaceId)) {
// case: invalid workspaceId passed
throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace"
});
}
if (environment) {
const isValid = isValidScopeV3({
authPayload: serviceTokenData,
environment,
secretPath,
requiredPermissions
});
if (!isValid) throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace"
});
}
};
export const RefreshTokenV3 = z.object({ export const RefreshTokenV3 = z.object({
body: z.object({ body: z.object({
@@ -71,20 +12,14 @@ export const CreateServiceTokenV3 = z.object({
name: z.string().trim(), name: z.string().trim(),
workspaceId: z.string().trim(), workspaceId: z.string().trim(),
publicKey: z.string().trim(), publicKey: z.string().trim(),
scopes: z role: z.string().trim().min(1).default(MEMBER),
.object({ trustedIps: z // TODO: provide default
permissions: z.enum(["read", "write"]).array(),
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
.min(1),
trustedIps: z
.object({ .object({
ipAddress: z.string().trim(), ipAddress: z.string().trim(),
}) })
.array() .array()
.min(1), .min(1)
.default([{ ipAddress: "0.0.0.0/0" }]),
expiresIn: z.number().optional(), expiresIn: z.number().optional(),
accessTokenTTL: z.number().int().min(1), accessTokenTTL: z.number().int().min(1),
encryptedKey: z.string().trim(), encryptedKey: z.string().trim(),
@@ -100,15 +35,7 @@ export const UpdateServiceTokenV3 = z.object({
body: z.object({ body: z.object({
name: z.string().trim().optional(), name: z.string().trim().optional(),
isActive: z.boolean().optional(), isActive: z.boolean().optional(),
scopes: z role: z.string().trim().min(1).optional(),
.object({
permissions: z.enum(["read", "write"]).array(),
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
.min(1)
.optional(),
trustedIps: z trustedIps: z
.object({ .object({
ipAddress: z.string().trim() ipAddress: z.string().trim()
+3
View File
@@ -15,6 +15,9 @@
"skipLibCheck": true, "skipLibCheck": true,
"typeRoots": ["./src/types", "./node_modules/@types"] "typeRoots": ["./src/types", "./node_modules/@types"]
}, },
"ts-node": {
"swc": true
},
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }
+1
View File
@@ -18,6 +18,7 @@ services:
redis: redis:
image: redis image: redis
container_name: infisical-dev-redis container_name: infisical-dev-redis
env_file: .env
environment: environment:
- ALLOW_EMPTY_PASSWORD=yes - ALLOW_EMPTY_PASSWORD=yes
ports: ports:
@@ -336,5 +336,9 @@
"step5-invite-team": "Invite your team", "step5-invite-team": "Invite your team",
"step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.", "step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.",
"step5-skip": "Skip" "step5-skip": "Skip"
},
"admin": {
"signup-title": "Admin Sign Up",
"dashboard": "Admin Dashboard"
} }
} }
@@ -2,7 +2,11 @@ import { useTranslation } from "react-i18next";
import { faWarning } from "@fortawesome/free-solid-svg-icons"; import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import issueBackupKey from "../utilities/cryptography/issueBackupKey"; import { useToggle } from "@app/hooks";
import { generateUserBackupKey } from "@app/lib/crypto";
import { useNotificationContext } from "../context/Notifications/NotificationProvider";
import { generateBackupPDFAsync } from "../utilities/generateBackupPDF";
import { Button } from "../v2"; import { Button } from "../v2";
interface DownloadBackupPDFStepProps { interface DownloadBackupPDFStepProps {
@@ -28,35 +32,56 @@ export default function DonwloadBackupPDFStep({
name name
}: DownloadBackupPDFStepProps): JSX.Element { }: DownloadBackupPDFStepProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
const { createNotification } = useNotificationContext();
const [isLoading, setIsLoading] = useToggle();
const handleBackupKeyGenerate = async () => {
try {
setIsLoading.on();
const generatedKey = await generateUserBackupKey(email, password);
await generateBackupPDFAsync({
generatedKey,
personalEmail: email,
personalName: name
});
incrementStep();
} catch (err) {
console.log(err);
createNotification({
type: "error",
text: "Faield to generate backup key"
});
} finally {
setIsLoading.off();
}
};
return ( return (
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16"> <div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16">
<p className="text-xl text-center font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200"> <p className="text-xl text-center font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />{t("signup.step4-message")} <FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />
{t("signup.step4-message")}
</p> </p>
<div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md"> <div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md">
<div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto"> <div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto">
<span className='mb-2'>{t("signup.step4-description1")} {t("signup.step4-description3")}</span> <span className="mb-2">
{t("signup.step4-description1")} {t("signup.step4-description3")}
</span>
</div> </div>
<div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left"> <div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left">
<div className="text-l py-1 text-lg w-full"> <div className="text-l py-1 text-lg w-full">
<Button <Button
onClick={async () => { onClick={handleBackupKeyGenerate}
await issueBackupKey({
email,
password,
personalName: name,
setBackupKeyError: () => { },
setBackupKeyIssued: () => { }
});
incrementStep();
}}
size="sm" size="sm"
isFullWidth isFullWidth
className='h-12' isLoading={isLoading}
isDisabled={isLoading}
className="h-12"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
> Download PDF </Button> >
Download PDF
</Button>
</div> </div>
</div> </div>
</div> </div>
+92 -113
View File
@@ -8,9 +8,6 @@ import Telemetry from "./telemetry/Telemetry";
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
import SecurityClient from "./SecurityClient"; import SecurityClient from "./SecurityClient";
// eslint-disable-next-line new-cap
const client = new jsrp.client();
interface IsLoginSuccessful { interface IsLoginSuccessful {
mfaEnabled: boolean; mfaEnabled: boolean;
success: boolean; success: boolean;
@@ -22,118 +19,100 @@ interface IsLoginSuccessful {
* @param {string} email - email of user to log in * @param {string} email - email of user to log in
* @param {string} password - password of user to log in * @param {string} password - password of user to log in
*/ */
const attemptLogin = async ( const attemptLogin = async ({
{ email,
email, password,
password, providerAuthToken
providerAuthToken, }: {
}: { email: string;
email: string; password: string;
password: string; providerAuthToken?: string;
providerAuthToken?: string; }): Promise<IsLoginSuccessful> => {
}
): Promise<IsLoginSuccessful> => {
const telemetry = new Telemetry().getInstance(); const telemetry = new Telemetry().getInstance();
return new Promise((resolve, reject) => { // eslint-disable-next-line new-cap
client.init( const client = new jsrp.client();
{ await new Promise((resolve) => {
username: email, client.init({ username: email, password }, () => resolve(null));
password
},
async () => {
try {
const clientPublicKey = client.getPublicKey();
const { serverPublicKey, salt } = await login1({
email,
clientPublicKey,
providerAuthToken,
});
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await login2(
{
email,
clientProof,
providerAuthToken,
}
);
if (mfaEnabled) {
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
resolve({
mfaEnabled,
success: true
});
} else if (
!mfaEnabled &&
encryptionVersion &&
encryptedPrivateKey &&
iv &&
tag &&
token
) {
// case: MFA is not enabled
// unset provider auth token in case it was used
SecurityClient.setProviderAuthToken("");
// set JWT token
SecurityClient.setToken(token);
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
resolve({
mfaEnabled: false,
success: true
});
}
} catch (err) {
reject(err);
}
}
);
}); });
const clientPublicKey = client.getPublicKey();
const { serverPublicKey, salt } = await login1({
email,
clientPublicKey,
providerAuthToken
});
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
const {
mfaEnabled,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
token,
publicKey,
encryptedPrivateKey,
iv,
tag
} = await login2({
email,
clientProof,
providerAuthToken
});
if (mfaEnabled) {
// case: MFA is enabled
// set temporary (MFA) JWT token
SecurityClient.setMfaToken(token);
return {
mfaEnabled,
success: true
};
}
if (!mfaEnabled && encryptionVersion && encryptedPrivateKey && iv && tag && token) {
// case: MFA is not enabled
// unset provider auth token in case it was used
SecurityClient.setProviderAuthToken("");
// set JWT token
SecurityClient.setToken(token);
const privateKey = await KeyService.decryptPrivateKey({
encryptionVersion,
encryptedPrivateKey,
iv,
tag,
password,
salt,
protectedKey,
protectedKeyIV,
protectedKeyTag
});
saveTokenToLocalStorage({
publicKey,
encryptedPrivateKey,
iv,
tag,
privateKey
});
if (email) {
telemetry.identify(email, email);
telemetry.capture("User Logged In");
}
return {
mfaEnabled: false,
success: true
};
}
return { success: false, mfaEnabled: false };
}; };
export default attemptLogin; export default attemptLogin;
@@ -1,6 +1,7 @@
import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries"; import { fetchOrgUsers, fetchUserAction } from "@app/hooks/api/users/queries";
interface OnboardingCheckProps { interface OnboardingCheckProps {
orgId: string;
setTotalOnboardingActionsDone?: (value: number) => void; setTotalOnboardingActionsDone?: (value: number) => void;
setHasUserClickedSlack?: (value: boolean) => void; setHasUserClickedSlack?: (value: boolean) => void;
setHasUserClickedIntro?: (value: boolean) => void; setHasUserClickedIntro?: (value: boolean) => void;
@@ -12,6 +13,7 @@ interface OnboardingCheckProps {
* This function checks which onboarding steps a user has already finished. * This function checks which onboarding steps a user has already finished.
*/ */
const onboardingCheck = async ({ const onboardingCheck = async ({
orgId,
setTotalOnboardingActionsDone, setTotalOnboardingActionsDone,
setHasUserClickedSlack, setHasUserClickedSlack,
setHasUserClickedIntro, setHasUserClickedIntro,
@@ -19,9 +21,7 @@ const onboardingCheck = async ({
setUsersInOrg setUsersInOrg
}: OnboardingCheckProps) => { }: OnboardingCheckProps) => {
let countActions = 0; let countActions = 0;
const userActionSlack = await fetchUserAction( const userActionSlack = await fetchUserAction("slack_cta_clicked");
"slack_cta_clicked"
);
if (userActionSlack) { if (userActionSlack) {
countActions += 1; countActions += 1;
@@ -41,9 +41,8 @@ const onboardingCheck = async ({
} }
if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro); if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro);
const orgId = localStorage.getItem("orgData.id");
const orgUsers = await fetchOrgUsers(orgId || ""); const orgUsers = await fetchOrgUsers(orgId || "");
if (orgUsers.length > 1) { if (orgUsers.length > 1) {
countActions += 1; countActions += 1;
} }
@@ -16,7 +16,6 @@ const yyyy = today.getFullYear();
const todayFormatted = `${mm}/${dd}/${yyyy}`; const todayFormatted = `${mm}/${dd}/${yyyy}`;
function createPdfHeader(doc: jsPDF, personalName: string) { function createPdfHeader(doc: jsPDF, personalName: string) {
doc.setFillColor(255, 255, 255); doc.setFillColor(255, 255, 255);
doc.rect(0, 0, 600, 900, "F"); doc.rect(0, 0, 600, 900, "F");
@@ -92,5 +91,15 @@ function generateBackupPDF({ personalName, personalEmail, generatedKey }: PDFPro
doc.save("Infisical Emergency Kit.pdf"); doc.save("Infisical Emergency Kit.pdf");
} }
export default generateBackupPDF; /**
* This function generate a pdf with a secret key for a user.
*/
export function generateBackupPDFAsync({ personalName, personalEmail, generatedKey }: PDFProps) {
// eslint-disable-next-line new-cap
const doc = new jsPDF("p", "pt", "a4", true);
createPdfHeader(doc, personalName);
createPdfContent(doc, personalEmail, generatedKey);
return doc.save("Infisical Emergency Kit.pdf", { returnPromise: true });
}
export default generateBackupPDF;
@@ -3,13 +3,15 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { twMerge } from "tailwind-merge";
type Props = { type Props = {
text?: string | string[]; text?: string | string[];
frequency?: number; frequency?: number;
className?: string;
}; };
export const ContentLoader = ({ text, frequency = 2000 }: Props) => { export const ContentLoader = ({ text, frequency = 2000, className }: Props) => {
const [pos, setPos] = useState(0); const [pos, setPos] = useState(0);
const isTextArray = Array.isArray(text); const isTextArray = Array.isArray(text);
useEffect(() => { useEffect(() => {
@@ -23,7 +25,12 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => {
}, []); }, []);
return ( return (
<div className="container mx-auto flex relative flex-col h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark] space-y-8"> <div
className={twMerge(
"container mx-auto flex relative flex-col h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark] space-y-8",
className
)}
>
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" /> <img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
{text && isTextArray && ( {text && isTextArray && (
<AnimatePresence exitBeforeEnter> <AnimatePresence exitBeforeEnter>
+1 -1
View File
@@ -3,7 +3,7 @@ import * as SwitchPrimitive from "@radix-ui/react-switch";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export type SwitchProps = Omit<SwitchPrimitive.SwitchProps, "checked" | "disabled" | "required"> & { export type SwitchProps = Omit<SwitchPrimitive.SwitchProps, "checked" | "disabled" | "required"> & {
children: ReactNode; children?: ReactNode;
id: string; id: string;
isChecked?: boolean; isChecked?: boolean;
isRequired?: boolean; isRequired?: boolean;
@@ -1,7 +1,5 @@
import { useOrganization, useSubscription } from "@app/context"; import { useOrganization, useSubscription } from "@app/context";
import { import { useGetOrgTrialUrl } from "@app/hooks/api";
useGetOrgTrialUrl
} from "@app/hooks/api";
import { Button } from "../Button"; import { Button } from "../Button";
import { Modal, ModalContent } from "../Modal"; import { Modal, ModalContent } from "../Modal";
@@ -16,38 +14,36 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const { mutateAsync, isLoading } = useGetOrgTrialUrl(); const { mutateAsync, isLoading } = useGetOrgTrialUrl();
const link = (subscription && subscription.slug !== null) const link =
? `/org/${currentOrg?._id}/billing` subscription && subscription.slug !== null
: "https://infisical.com/scheduledemo"; ? `/org/${currentOrg?._id}/billing`
: "https://infisical.com/scheduledemo";
const handleUpgradeBtnClick = async () => { const handleUpgradeBtnClick = async () => {
try { try {
if (!subscription || !currentOrg) return; if (!subscription || !currentOrg) return;
if (!subscription.has_used_trial) { if (!subscription.has_used_trial) {
// direct user to start pro trial // direct user to start pro trial
const url = await mutateAsync({ const url = await mutateAsync({
orgId: currentOrg._id, orgId: currentOrg._id,
success_url: window.location.href success_url: window.location.href
}); });
window.location.href = url; window.location.href = url;
} else { } else {
// direct user to upgrade their plan // direct user to upgrade their plan
window.location.href = link; window.location.href = link;
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
} };
return ( return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}> <Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent <ModalContent title="Unleash Infisical's Full Power">
title="Unleash Infisical's Full Power"
>
<p className="mb-2 text-bunker-300">{text}</p> <p className="mb-2 text-bunker-300">{text}</p>
<p className="text-bunker-300"> <p className="text-bunker-300">
Upgrade and get access to this, as well as to other powerful enhancements. Upgrade and get access to this, as well as to other powerful enhancements.
@@ -59,10 +55,10 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
onClick={handleUpgradeBtnClick} onClick={handleUpgradeBtnClick}
className="mr-4" className="mr-4"
> >
{(subscription && !subscription.has_used_trial) ? "Start Pro Free Trial" : "Upgrade Plan"} {subscription && !subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
</Button> </Button>
<Button <Button
colorSchema="secondary" colorSchema="secondary"
variant="plain" variant="plain"
onClick={() => onOpenChange && onOpenChange(false)} onClick={() => onOpenChange && onOpenChange(false)}
> >
@@ -71,5 +67,5 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele
</div> </div>
</ModalContent> </ModalContent>
</Modal> </Modal>
) );
} };
+6 -4
View File
@@ -21,7 +21,8 @@ export const publicPaths = [
"/saml-sso", "/saml-sso",
"/login/provider/success", // TODO: change "/login/provider/success", // TODO: change
"/login/provider/error", // TODO: change "/login/provider/error", // TODO: change
"/login/sso" "/login/sso",
"/admin/signup"
]; ];
export const languageMap = { export const languageMap = {
@@ -50,7 +51,8 @@ const plansProd: Mapping = {
export const plans = plansProd || plansDev; export const plans = plansProd || plansDev;
export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?"; export const leaveConfirmDefaultMessage =
"Your changes will be lost if you leave the page. Are you sure you want to continue?";
export const secretTagsColors = [ export const secretTagsColors = [
{ {
@@ -115,5 +117,5 @@ export const secretTagsColors = [
rgba: "rgb(255,0,0, 0.8)", rgba: "rgb(255,0,0, 0.8)",
name: "Red", name: "Red",
selected: false selected: false
}, }
] ];
@@ -18,7 +18,7 @@ type Props = {
// Provide a context for whole app to notify user is authorized or not // Provide a context for whole app to notify user is authorized or not
export const AuthProvider = ({ children }: Props): JSX.Element => { export const AuthProvider = ({ children }: Props): JSX.Element => {
const { isLoading } = useGetAuthToken(); const { isLoading } = useGetAuthToken();
const { pathname, push } = useRouter(); const { pathname, push, asPath } = useRouter();
const [isReady, setIsReady] = useToggle(false); const [isReady, setIsReady] = useToggle(false);
useEffect(() => { useEffect(() => {
@@ -26,7 +26,7 @@ export const AuthProvider = ({ children }: Props): JSX.Element => {
if (!isLoading) { if (!isLoading) {
// not a public path and not authenticated kick to login page // not a public path and not authenticated kick to login page
if (!publicPaths.includes(pathname) && !isLoggedIn()) { if (!publicPaths.includes(pathname) && !isLoggedIn()) {
push("/login").then(() => { push({ pathname: "/login", query: { redirect: asPath } }).then(() => {
setIsReady.on(); setIsReady.on();
}); });
} else { } else {
@@ -40,7 +40,12 @@ export const AuthProvider = ({ children }: Props): JSX.Element => {
if (isLoading || !isReady) { if (isLoading || !isReady) {
return ( return (
<div className="flex items-center justify-center w-screen h-screen bg-bunker-800"> <div className="flex items-center justify-center w-screen h-screen bg-bunker-800">
<img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> <img
src="/images/loading/loading.gif"
height={70}
width={120}
alt="infisical loading indicator"
/>
</div> </div>
); );
} }
@@ -0,0 +1,53 @@
import { createContext, ReactNode, useContext, useEffect, useMemo } from "react";
import { useRouter } from "next/router";
import { ContentLoader } from "@app/components/v2/ContentLoader";
import { useGetServerConfig } from "@app/hooks/api";
import { TServerConfig } from "@app/hooks/api/admin/types";
type TServerConfigContext = {
config: TServerConfig;
};
const ServerConfigContext = createContext<TServerConfigContext | null>(null);
type Props = {
children: ReactNode;
};
export const ServerConfigProvider = ({ children }: Props): JSX.Element => {
const router = useRouter();
const { data, isLoading } = useGetServerConfig();
// memorize the workspace details for the context
const value = useMemo<TServerConfigContext>(() => {
return {
config: data!
};
}, [data]);
useEffect(() => {
if (!isLoading && data && !data.initialized) {
router.push("/admin/signup");
}
}, [isLoading, data]);
if (isLoading || (!data?.initialized && router.pathname !== "/admin/signup")) {
return (
<div className="bg-bunker-800">
<ContentLoader text="Loading configurations" />
</div>
);
}
return <ServerConfigContext.Provider value={value}>{children}</ServerConfigContext.Provider>;
};
export const useServerConfig = () => {
const ctx = useContext(ServerConfigContext);
if (!ctx) {
throw new Error("useServerConfig has to be used within <UserContext.Provider>");
}
return ctx;
};
@@ -0,0 +1 @@
export { ServerConfigProvider,useServerConfig } from "./ServerConfigContext";
+1
View File
@@ -14,6 +14,7 @@ export {
ProjectPermissionSub, ProjectPermissionSub,
useProjectPermission useProjectPermission
} from "./ProjectPermissionContext"; } from "./ProjectPermissionContext";
export { ServerConfigProvider,useServerConfig } from "./ServerConfigContext";
export { SubscriptionProvider, useSubscription } from "./SubscriptionContext"; export { SubscriptionProvider, useSubscription } from "./SubscriptionContext";
export { UserProvider, useUser } from "./UserContext"; export { UserProvider, useUser } from "./UserContext";
export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext"; export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext";
+2
View File
@@ -0,0 +1,2 @@
export { useCreateAdminUser, useUpdateServerConfig } from "./mutation";
export { useGetServerConfig } from "./queries";
+39
View File
@@ -0,0 +1,39 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { User } from "../users/types";
import { adminQueryKeys } from "./queries";
import { TCreateAdminUserDTO, TServerConfig } from "./types";
export const useCreateAdminUser = () => {
const queryClient = useQueryClient();
return useMutation<{ user: User; token: string }, {}, TCreateAdminUserDTO>({
mutationFn: async (opt) => {
const { data } = await apiRequest.post("/api/v1/admin/signup", opt);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries(adminQueryKeys.serverConfig());
}
});
};
export const useUpdateServerConfig = () => {
const queryClient = useQueryClient();
return useMutation<TServerConfig, {}, Partial<TServerConfig>>({
mutationFn: async (opt) => {
const { data } = await apiRequest.patch<{ config: TServerConfig }>(
"/api/v1/admin/config",
opt
);
return data.config;
},
onSuccess: (data) => {
queryClient.setQueryData(adminQueryKeys.serverConfig(), data);
queryClient.invalidateQueries(adminQueryKeys.serverConfig());
}
});
};
+34
View File
@@ -0,0 +1,34 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TServerConfig } from "./types";
export const adminQueryKeys = {
serverConfig: () => ["server-config"] as const
};
const fetchServerConfig = async () => {
const { data } = await apiRequest.get<{ config: TServerConfig }>("/api/v1/admin/config");
return data.config;
};
export const useGetServerConfig = ({
options = {}
}: {
options?: Omit<
UseQueryOptions<
TServerConfig,
unknown,
TServerConfig,
ReturnType<typeof adminQueryKeys.serverConfig>
>,
"queryKey" | "queryFn"
>;
} = {}) =>
useQuery({
queryKey: adminQueryKeys.serverConfig(),
queryFn: fetchServerConfig,
...options,
enabled: options?.enabled ?? true
});
+19
View File
@@ -0,0 +1,19 @@
export type TServerConfig = {
initialized: boolean;
allowSignUp: boolean;
};
export type TCreateAdminUserDTO = {
email: string;
firstName: string;
lastName?: string;
protectedKey: string;
protectedKeyTag: string;
protectedKeyIV: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
publicKey: string;
verifier: string;
salt: string;
};
+3 -14
View File
@@ -4,17 +4,6 @@ import {
UserAgentType UserAgentType
} from "./enums"; } from "./enums";
enum Permission {
READ = "read",
READ_WRITE = "readWrite"
}
interface Scope {
environment: string;
secretPath: string;
permission: Permission;
}
interface UserActorMetadata { interface UserActorMetadata {
userId: string; userId: string;
email: string; email: string;
@@ -211,7 +200,7 @@ interface CreateServiceTokenV3Event {
metadata: { metadata: {
name: string; name: string;
isActive: boolean; isActive: boolean;
scopes: Array<Scope>; role: string;
expiresAt?: Date; expiresAt?: Date;
} }
} }
@@ -221,7 +210,7 @@ interface UpdateServiceTokenV3Event {
metadata: { metadata: {
name?: string; name?: string;
isActive?: boolean; isActive?: boolean;
scopes?: Array<Scope>; role?: string;
expiresAt?: Date; expiresAt?: Date;
} }
} }
@@ -231,7 +220,7 @@ interface DeleteServiceTokenV3Event {
metadata: { metadata: {
name: string; name: string;
isActive: boolean; isActive: boolean;
scopes: Array<Scope>; role?: string;
expiresAt?: Date; expiresAt?: Date;
} }
} }
+1
View File
@@ -1,4 +1,5 @@
export * from "./apiKeys"; export * from "./apiKeys";
export * from "./admin"
export * from "./auditLogs"; export * from "./auditLogs";
export * from "./auth"; export * from "./auth";
export * from "./bots"; export * from "./bots";
@@ -85,8 +85,8 @@ export const useUpdateServiceTokenV3 = () => {
mutationFn: async ({ mutationFn: async ({
serviceTokenDataId, serviceTokenDataId,
name, name,
role,
isActive, isActive,
scopes,
trustedIps, trustedIps,
expiresIn, expiresIn,
accessTokenTTL, accessTokenTTL,
@@ -94,8 +94,8 @@ export const useUpdateServiceTokenV3 = () => {
}) => { }) => {
const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, { const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, {
name, name,
role,
isActive, isActive,
scopes,
trustedIps, trustedIps,
expiresIn, expiresIn,
accessTokenTTL, accessTokenTTL,
+7 -11
View File
@@ -1,5 +1,3 @@
import { Permission } from "./enums";
export type ServiceTokenScope = { export type ServiceTokenScope = {
environment: string; environment: string;
secretPath: string; secretPath: string;
@@ -38,12 +36,6 @@ export type DeleteServiceTokenRes = { serviceTokenData: ServiceToken };
// --- v3 // --- v3
export type ServiceTokenV3Scope = {
permissions: Permission[];
environment: string;
secretPath: string;
};
export type ServiceTokenV3TrustedIp = { export type ServiceTokenV3TrustedIp = {
_id: string; _id: string;
ipAddress: string; ipAddress: string;
@@ -54,13 +46,17 @@ export type ServiceTokenV3TrustedIp = {
export type ServiceTokenDataV3 = { export type ServiceTokenDataV3 = {
_id: string; _id: string;
name: string; name: string;
role: string;
customRole?: {
name: string;
slug: string;
};
workspace: string; workspace: string;
isActive: boolean; isActive: boolean;
refreshTokenLastUsed?: string; refreshTokenLastUsed?: string;
accessTokenLastUsed?: string; accessTokenLastUsed?: string;
refreshTokenUsageCount: number; refreshTokenUsageCount: number;
accessTokenUsageCount: number; accessTokenUsageCount: number;
scopes: ServiceTokenV3Scope[];
trustedIps: ServiceTokenV3TrustedIp[]; trustedIps: ServiceTokenV3TrustedIp[];
expiresAt?: string; expiresAt?: string;
accessTokenTTL: number; accessTokenTTL: number;
@@ -71,9 +67,9 @@ export type ServiceTokenDataV3 = {
export type CreateServiceTokenDataV3DTO = { export type CreateServiceTokenDataV3DTO = {
name: string; name: string;
role?: string;
workspaceId: string; workspaceId: string;
publicKey: string; publicKey: string;
scopes: ServiceTokenV3Scope[];
trustedIps: { trustedIps: {
ipAddress: string; ipAddress: string;
}[]; }[];
@@ -93,7 +89,7 @@ export type UpdateServiceTokenDataV3DTO = {
serviceTokenDataId: string; serviceTokenDataId: string;
isActive?: boolean; isActive?: boolean;
name?: string; name?: string;
scopes?: ServiceTokenV3Scope[]; role?: string;
trustedIps?: { trustedIps?: {
ipAddress: string; ipAddress: string;
}[]; }[];
+1
View File
@@ -14,6 +14,7 @@ export type User = {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
email: string; email: string;
superAdmin: boolean;
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
authProvider?: AuthMethod; authProvider?: AuthMethod;
@@ -7,7 +7,6 @@ export {
useDeleteWsEnvironment, useDeleteWsEnvironment,
useGetUserWorkspaceMemberships, useGetUserWorkspaceMemberships,
useGetUserWorkspaces, useGetUserWorkspaces,
useGetUserWsEnvironments,
useGetWorkspaceAuthorizations, useGetWorkspaceAuthorizations,
useGetWorkspaceById, useGetWorkspaceById,
useGetWorkspaceIndexStatus, useGetWorkspaceIndexStatus,
+1 -19
View File
@@ -12,14 +12,12 @@ import {
CreateWorkspaceDTO, CreateWorkspaceDTO,
DeleteEnvironmentDTO, DeleteEnvironmentDTO,
DeleteWorkspaceDTO, DeleteWorkspaceDTO,
GetWsEnvironmentDTO,
NameWorkspaceSecretsDTO, NameWorkspaceSecretsDTO,
RenameWorkspaceDTO, RenameWorkspaceDTO,
ReorderEnvironmentsDTO, ReorderEnvironmentsDTO,
ToggleAutoCapitalizationDTO, ToggleAutoCapitalizationDTO,
UpdateEnvironmentDTO, UpdateEnvironmentDTO,
Workspace, Workspace
WorkspaceEnv
} from "./types"; } from "./types";
export const workspaceKeys = { export const workspaceKeys = {
@@ -31,7 +29,6 @@ export const workspaceKeys = {
getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"], getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"],
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"], getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: ["workspaces"] as const, getAllUserWorkspace: ["workspaces"] as const,
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const,
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const, getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const, getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const,
getWorkspaceServiceTokenDataV3: (workspaceId: string) => getWorkspaceServiceTokenDataV3: (workspaceId: string) =>
@@ -103,21 +100,6 @@ const fetchUserWorkspaceMemberships = async (orgId: string) => {
return data; return data;
}; };
const fetchUserWsEnvironments = async (workspaceId: string) => {
const { data } = await apiRequest.get<{ accessibleEnvironments: WorkspaceEnv[] }>(
`/api/v2/workspace/${workspaceId}/environments`
);
return data.accessibleEnvironments;
};
export const useGetUserWsEnvironments = ({ workspaceId, onSuccess }: GetWsEnvironmentDTO) =>
useQuery({
enabled: Boolean(workspaceId),
onSuccess,
queryKey: workspaceKeys.getUserWsEnvironments(workspaceId),
queryFn: () => fetchUserWsEnvironments(workspaceId)
});
// to get all userids in an org with the workspace they are part of // to get all userids in an org with the workspace they are part of
export const useGetUserWorkspaceMemberships = (orgId: string) => export const useGetUserWorkspaceMemberships = (orgId: string) =>
useQuery({ useQuery({
@@ -30,11 +30,6 @@ export type CreateWorkspaceDTO = {
organizationId: string; organizationId: string;
}; };
export type GetWsEnvironmentDTO = {
workspaceId: string;
onSuccess?: (data: WorkspaceEnv[]) => void;
};
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean }; export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
@@ -0,0 +1,306 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable vars-on-top */
/* eslint-disable no-var */
/* eslint-disable func-names */
// @ts-nocheck
import { useTranslation } from "react-i18next";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
import {
faArrowLeft,
faArrowUpRightFromSquare,
faBook,
faEnvelope,
faInfinity,
faInfo,
faMobile,
faPlus,
faQuestion
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@app/components/v2";
import { useOrganization, useSubscription, useUser } from "@app/context";
import {
useGetOrgTrialUrl,
useGetUserAction,
useLogoutUser,
useRegisterUserAction
} from "@app/hooks/api";
interface LayoutProps {
children: React.ReactNode;
}
const supportOptions = [
[
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
"Support Forum",
"https://infisical.com/slack"
],
[
<FontAwesomeIcon key={2} className="pr-4 text-sm" icon={faBook} />,
"Read Docs",
"https://infisical.com/docs/documentation/getting-started/introduction"
],
[
<FontAwesomeIcon key={3} className="pr-4 text-sm" icon={faGithub} />,
"GitHub Issues",
"https://github.com/Infisical/infisical/issues"
],
[
<FontAwesomeIcon key={4} className="pr-4 text-sm" icon={faEnvelope} />,
"Email Support",
"mailto:[email protected]"
]
];
export const AdminLayout = ({ children }: LayoutProps) => {
const router = useRouter();
const { mutateAsync } = useGetOrgTrialUrl();
// eslint-disable-next-line prefer-const
const { currentOrg } = useOrganization();
const { user } = useUser();
const { subscription } = useSubscription();
const { data: updateClosed } = useGetUserAction("september_update_closed");
const infisicalPlatformVersion = process.env.NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION;
const { t } = useTranslation();
const registerUserAction = useRegisterUserAction();
const closeUpdate = async () => {
await registerUserAction.mutateAsync("september_update_closed");
};
const logout = useLogoutUser();
const logOutUser = async () => {
try {
console.log("Logging out...");
await logout.mutateAsync();
router.push("/login");
} catch (error) {
console.error(error);
}
};
return (
<>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div>
{!router.asPath.includes("personal") && (
<div className="flex h-12 cursor-default justify-between items-center px-3 pt-6">
<Link href={`/org/${currentOrg?._id}/overview`}>
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
Back to organization
</div>
</Link>
<DropdownMenu>
<DropdownMenuTrigger
asChild
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
>
<div
className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
style={{ fontSize: "11px", width: "26px", height: "26px" }}
>
{user?.firstName?.charAt(0)}
{user?.lastName && user?.lastName?.charAt(0)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.email}</div>
<Link href="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
</Link>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Documentation
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
<a
href="https://infisical.com/slack"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<DropdownMenuItem>
Join Slack Community
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] pl-1.5 text-xxs"
/>
</DropdownMenuItem>
</a>
{user?.superAdmin && (
<Link href="/admin" legacyBehavior>
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Admin Panel
</DropdownMenuItem>
</Link>
)}
<div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem>
</button>
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</div>
<div
className={`relative mt-10 ${
subscription && subscription.slug === "starter" && !subscription.has_used_trial
? "mb-2"
: "mb-4"
} flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400`}
>
<div
className={`${
!updateClosed ? "block" : "hidden"
} relative z-10 mb-6 flex pb-2 w-52 flex-col items-center justify-start rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3`}
>
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
Infisical September update
</div>
<div className="mt-1 mb-1 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300">
Improved RBAC, new integrations, dashboard remake, and more!
</div>
<div className="mt-2 h-[6.77rem] w-full rounded-md border border-mineshaft-700">
<Image
src="/images/infisical-update-september-2023.png"
height={319}
width={539}
alt="kubernetes image"
className="rounded-sm"
/>
</div>
<div className="mt-3 flex w-full items-center justify-between px-0.5">
<button
type="button"
onClick={() => closeUpdate()}
className="text-mineshaft-400 duration-200 hover:text-mineshaft-100"
>
Close
</button>
<a
href="https://infisical.com/blog/infisical-update-september-2023"
target="_blank"
rel="noopener noreferrer"
className="text-sm font-normal leading-[1.2rem] text-mineshaft-400 duration-200 hover:text-mineshaft-100"
>
Learn More{" "}
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="pl-0.5 text-xs" />
</a>
</div>
</div>
{router.asPath.includes("org") && (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={() => router.push(`/org/${router.query.id}/members?action=invite`)}
className="w-full"
>
<div className="mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faPlus} className="mr-3" />
Invite people
</div>
</div>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
{supportOptions.map(([icon, text, url]) => (
<DropdownMenuItem key={url}>
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
{subscription &&
subscription.slug === "starter" &&
!subscription.has_used_trial && (
<button
type="button"
onClick={async () => {
if (!subscription || !currentOrg) return;
// direct user to start pro trial
const url = await mutateAsync({
orgId: currentOrg._id,
success_url: window.location.href
});
window.location.href = url;
}}
className="mt-1.5 w-full"
>
<div className="justify-left mb-1.5 mt-1.5 flex w-full items-center rounded-md bg-mineshaft-600 py-1 pl-4 text-mineshaft-300 duration-200 hover:bg-mineshaft-500 hover:text-primary-400">
<FontAwesomeIcon
icon={faInfinity}
className="mr-3 ml-0.5 py-2 text-primary"
/>
Start Free Pro Trial
</div>
</button>
)}
{infisicalPlatformVersion && (
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {infisicalPlatformVersion}
</div>
)}
</div>
</nav>
</aside>
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
{children}
</main>
</div>
</div>
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t("common.no-mobile")} `}
</p>
</div>
</>
);
};
@@ -0,0 +1 @@
export { AdminLayout } from "./AdminLayout";
+8 -14
View File
@@ -399,6 +399,13 @@ export const AppLayout = ({ children }: LayoutProps) => {
/> />
</DropdownMenuItem> </DropdownMenuItem>
</a> </a>
{user?.superAdmin && (
<Link href="/admin" legacyBehavior>
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
Admin Panel
</DropdownMenuItem>
</Link>
)}
<div className="mt-1 h-1 border-t border-mineshaft-600" /> <div className="mt-1 h-1 border-t border-mineshaft-600" />
<button type="button" onClick={logOutUser} className="w-full"> <button type="button" onClick={logOutUser} className="w-full">
<DropdownMenuItem>Log Out</DropdownMenuItem> <DropdownMenuItem>Log Out</DropdownMenuItem>
@@ -498,7 +505,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
} }
icon="system-outline-96-groups" icon="system-outline-96-groups"
> >
{t("nav.menu.members")} Access Control
</MenuItem> </MenuItem>
</a> </a>
</Link> </Link>
@@ -541,19 +548,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
</MenuItem> </MenuItem>
</a> </a>
</Link> </Link>
{/* <Link href={`/project/${currentWorkspace?._id}/allowlist`} passHref>
<a>
<MenuItem
isSelected={
router.asPath === `/project/${currentWorkspace?._id}/allowlist`
}
icon="system-outline-126-verified"
>
IP Allowlist
</MenuItem>
</a>
</Link> */}
<Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref> <Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
<a> <a>
<MenuItem <MenuItem
+1
View File
@@ -1 +1,2 @@
export { AdminLayout } from "./AdminLayout";
export { AppLayout } from "./AppLayout"; export { AppLayout } from "./AppLayout";
+126
View File
@@ -0,0 +1,126 @@
import crypto from "crypto";
import jsrp from "jsrp";
import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util";
import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm";
import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto";
import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
export const generateUserBackupKey = async (email: string, password: string) => {
// eslint-disable-next-line new-cap
const clientKey = new jsrp.client();
// eslint-disable-next-line new-cap
const clientPassword = new jsrp.client();
await new Promise((resolve) => {
clientPassword.init({ username: email, password }, () => resolve(null));
});
const clientPublicKey = clientPassword.getPublicKey();
const srpKeys = await srp1({ clientPublicKey });
clientPassword.setSalt(srpKeys.salt);
clientPassword.setServerPublicKey(srpKeys.serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
await new Promise((resolve) => {
clientKey.init({ username: email, password: generatedKey }, () => resolve(null));
});
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>(
(resolve, reject) => {
clientKey.createVerifier((err, res) => {
if (err) return reject(err);
return resolve(res);
});
}
);
const { ciphertext, iv, tag } = Aes256Gcm.encrypt({
text: String(localStorage.getItem("PRIVATE_KEY")),
secret: generatedKey
});
await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt,
verifier,
clientProof
});
return generatedKey;
};
export const generateUserPassKey = async (email: string, password: string) => {
// eslint-disable-next-line new-cap
const client = new jsrp.client();
const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey;
const privateKey = encodeBase64(secretKeyUint8Array);
const publicKey = encodeBase64(publicKeyUint8Array);
await new Promise((resolve) => {
client.init({ username: email, password }, () => resolve(null));
});
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>(
(resolve, reject) => {
client.createVerifier((err, res) => {
if (err) return reject(err);
return resolve(res);
});
}
);
const derivedKey = await deriveArgonKey({
password,
salt,
mem: 65536,
time: 3,
parallelism: 1,
hashLen: 32
});
if (!derivedKey) throw new Error("Failed to derive key from password");
const key = crypto.randomBytes(32);
// create encrypted private key by encrypting the private
// key with the symmetric key [key]
const {
ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag
} = Aes256Gcm.encrypt({
text: privateKey,
secret: key
});
// create the protected key by encrypting the symmetric key
// [key] with the derived key
const {
ciphertext: protectedKey,
iv: protectedKeyIV,
tag: protectedKeyTag
} = Aes256Gcm.encrypt({
text: key.toString("hex"),
secret: Buffer.from(derivedKey.hash)
});
return {
protectedKey,
protectedKeyTag,
protectedKeyIV,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
publicKey,
verifier,
salt,
privateKey
};
};
+29 -22
View File
@@ -20,6 +20,7 @@ import {
OrgPermissionProvider, OrgPermissionProvider,
OrgProvider, OrgProvider,
ProjectPermissionProvider, ProjectPermissionProvider,
ServerConfigProvider,
SubscriptionProvider, SubscriptionProvider,
UserProvider, UserProvider,
WorkspaceProvider WorkspaceProvider
@@ -84,36 +85,42 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<NotificationProvider> <NotificationProvider>
<AuthProvider> <ServerConfigProvider>
<Component {...pageProps} /> <AuthProvider>
</AuthProvider> <Component {...pageProps} />
</AuthProvider>
</ServerConfigProvider>
</NotificationProvider> </NotificationProvider>
</QueryClientProvider> </QueryClientProvider>
); );
} }
const Layout = Component?.layout || AppLayout;
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<TooltipProvider> <TooltipProvider>
<AuthProvider> <NotificationProvider>
<OrgProvider> <ServerConfigProvider>
<OrgPermissionProvider> <AuthProvider>
<WorkspaceProvider> <OrgProvider>
<ProjectPermissionProvider> <OrgPermissionProvider>
<SubscriptionProvider> <WorkspaceProvider>
<UserProvider> <ProjectPermissionProvider>
<NotificationProvider> <SubscriptionProvider>
<AppLayout> <UserProvider>
<Component {...pageProps} /> <Layout>
</AppLayout> <Component {...pageProps} />
</NotificationProvider> </Layout>
</UserProvider> </UserProvider>
</SubscriptionProvider> </SubscriptionProvider>
</ProjectPermissionProvider> </ProjectPermissionProvider>
</WorkspaceProvider> </WorkspaceProvider>
</OrgPermissionProvider> </OrgPermissionProvider>
</OrgProvider> </OrgProvider>
</AuthProvider> </AuthProvider>
</ServerConfigProvider>
</NotificationProvider>
</TooltipProvider> </TooltipProvider>
</QueryClientProvider> </QueryClientProvider>
); );
+30
View File
@@ -0,0 +1,30 @@
import { useTranslation } from "react-i18next";
import Head from "next/head";
import { AdminLayout } from "@app/layouts";
import { AdminDashboardPage } from "@app/views/admin/DashboardPage";
const AdminDashboard = () => {
const { t } = useTranslation();
return (
<>
<Head>
<title>{t("common.head-title", { title: t("admin.dashboard") })}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content={t("admin.dashboard.og-title") ?? ""} />
<meta name="og:description" content={t("admin.dashboard.og-description") ?? ""} />
</Head>
<div className="h-full">
<AdminDashboardPage />
</div>
</>
);
};
export default AdminDashboard;
AdminDashboard.requireAuth = true;
AdminDashboard.layout = AdminLayout;
+21
View File
@@ -0,0 +1,21 @@
import { useTranslation } from "react-i18next";
import Head from "next/head";
import { SignUpPage } from "@app/views/admin/SignUpPage";
export default function LoginPage() {
const { t } = useTranslation();
return (
<div className="flex min-h-screen max-h-screen overflow-y-auto flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6">
<Head>
<title>{t("common.head-title", { title: t("signup.title") })}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
<meta property="og:title" content={t("signup.og-title") ?? ""} />
<meta name="og:description" content={t("signup.og-description") ?? ""} />
</Head>
<SignUpPage />
</div>
);
}
@@ -559,6 +559,7 @@ const OrganizationPage = withPermission(
useEffect(() => { useEffect(() => {
onboardingCheck({ onboardingCheck({
orgId: currentOrg,
setHasUserClickedIntro, setHasUserClickedIntro,
setHasUserClickedSlack, setHasUserClickedSlack,
setHasUserPushedSecrets, setHasUserPushedSecrets,
+9 -1
View File
@@ -12,6 +12,7 @@ import InitialSignupStep from "@app/components/signup/InitialSignupStep";
import TeamInviteStep from "@app/components/signup/TeamInviteStep"; import TeamInviteStep from "@app/components/signup/TeamInviteStep";
import UserInfoStep from "@app/components/signup/UserInfoStep"; import UserInfoStep from "@app/components/signup/UserInfoStep";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { useServerConfig } from "@app/context";
import { useVerifyEmailVerificationCode } from "@app/hooks/api"; import { useVerifyEmailVerificationCode } from "@app/hooks/api";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
@@ -34,6 +35,13 @@ export default function SignUp() {
const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false); const [isCodeInputCheckLoading, setIsCodeInputCheckLoading] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const { mutateAsync } = useVerifyEmailVerificationCode(); const { mutateAsync } = useVerifyEmailVerificationCode();
const { config } = useServerConfig();
useEffect(() => {
if (!config.allowSignUp) {
router.push("/login");
}
}, [config.allowSignUp]);
useEffect(() => { useEffect(() => {
const tryAuth = async () => { const tryAuth = async () => {
@@ -65,7 +73,7 @@ export default function SignUp() {
const { token } = await mutateAsync({ email, code }); const { token } = await mutateAsync({ email, code });
SecurityClient.setSignupToken(token); SecurityClient.setSignupToken(token);
setStep(3); setStep(3);
} catch(err) { } catch (err) {
console.error(err); console.error(err);
setCodeError(true); setCodeError(true);
} }
@@ -12,7 +12,7 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
import attemptCliLogin from "@app/components/utilities/attemptCliLogin"; import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
import attemptLogin from "@app/components/utilities/attemptLogin"; import attemptLogin from "@app/components/utilities/attemptLogin";
import { Button, Input } from "@app/components/v2"; import { Button, Input } from "@app/components/v2";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { useServerConfig } from "@app/context";
import { navigateUserToOrg } from "../../Login.utils"; import { navigateUserToOrg } from "../../Login.utils";
@@ -30,7 +30,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
const { t } = useTranslation(); const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [loginError, setLoginError] = useState(false); const [loginError, setLoginError] = useState(false);
const { data: serverDetails } = useFetchServerStatus(); const { config } = useServerConfig();
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const handleLogin = async (e: FormEvent<HTMLFormElement>) => { const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
@@ -84,7 +84,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
setIsLoading(false); setIsLoading(false);
return; return;
} }
await navigateUserToOrg(router); await navigateUserToOrg(router);
// case: login does not require MFA step // case: login does not require MFA step
@@ -225,7 +225,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
</Button> </Button>
</div> </div>
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />} {!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
{!serverDetails?.inviteOnlySignup ? ( {config.allowSignUp ? (
<div className="mt-6 flex flex-row text-sm text-bunker-400"> <div className="mt-6 flex flex-row text-sm text-bunker-400">
<Link href="/signup"> <Link href="/signup">
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4"> <span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
@@ -236,7 +236,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
) : ( ) : (
<div /> <div />
)} )}
<div className="flex flex-row text-sm text-bunker-400"> <div className="flex flex-row text-sm text-bunker-400 mt-2">
<Link href="/verify-email"> <Link href="/verify-email">
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4"> <span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
Forgot password? Recover your account Forgot password? Recover your account

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