mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Implement preliminary pentest remediations
This commit is contained in:
@@ -89,7 +89,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
const { email, clientProof } = req.body;
|
||||
const user = await User.findOne({
|
||||
email
|
||||
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
|
||||
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag +devices');
|
||||
|
||||
if (!user) throw new Error('Failed to find user');
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ import { createToken } from '../../helpers/auth';
|
||||
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
||||
import { sendMail } from '../../helpers/nodemailer';
|
||||
import { TokenService } from '../../services';
|
||||
import { EELicenseService } from '../../ee/services';
|
||||
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables';
|
||||
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
|
||||
import { validateUserEmail } from '../../validation';
|
||||
|
||||
/**
|
||||
* Delete organization membership with id [membershipOrgId] from organization
|
||||
@@ -115,6 +117,19 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
if (!membershipOrg) {
|
||||
throw new Error('Failed to validate organization membership');
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getOrganizationPlan(organizationId);
|
||||
|
||||
if (plan.memberLimit !== null) {
|
||||
// case: limit imposed on number of members allowed
|
||||
|
||||
if (plan.membersUsed >= plan.memberLimit) {
|
||||
// case: number of members used exceeds the number of members allowed
|
||||
return res.status(400).send({
|
||||
message: 'Failed to invite member due to member limit reached. Upgrade plan to invite more members.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
invitee = await User.findOne({
|
||||
email: inviteeEmail
|
||||
@@ -153,6 +168,9 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
|
||||
if (!inviteeMembershipOrg) {
|
||||
// case: invitee has never been invited before
|
||||
|
||||
// validate that email is not disposable
|
||||
validateUserEmail(inviteeEmail);
|
||||
|
||||
await new MembershipOrg({
|
||||
inviteEmail: inviteeEmail,
|
||||
|
||||
@@ -28,7 +28,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
|
||||
// case: user has already completed account
|
||||
|
||||
return res.status(403).send({
|
||||
error: 'Failed to send email verification for password reset'
|
||||
message: "If an account exists with this email, a password reset link has been sent"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
message: `Sent an email for account recovery to ${email}`
|
||||
message:"If an account exists with this email, a password reset link has been sent"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { createToken } from '../../helpers/auth';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
|
||||
import { validateUserEmail } from '../../validation';
|
||||
|
||||
/**
|
||||
* Signup step 1: Initialize account for user under email [email] and send a verification code
|
||||
@@ -20,6 +21,9 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||
let email: string;
|
||||
try {
|
||||
email = req.body.email;
|
||||
|
||||
// validate that email is not disposable
|
||||
validateUserEmail(email);
|
||||
|
||||
const user = await User.findOne({ email }).select('+publicKey');
|
||||
if (user && user?.publicKey) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
createWorkspace as create,
|
||||
deleteWorkspace as deleteWork,
|
||||
} from "../../helpers/workspace";
|
||||
import { EELicenseService } from '../../ee/services';
|
||||
import { addMemberships } from "../../helpers/membership";
|
||||
import { ADMIN } from "../../variables";
|
||||
|
||||
@@ -141,6 +142,7 @@ export const getWorkspace = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const createWorkspace = async (req: Request, res: Response) => {
|
||||
let workspace;
|
||||
|
||||
try {
|
||||
const { workspaceName, organizationId } = req.body;
|
||||
|
||||
@@ -154,6 +156,18 @@ export const createWorkspace = async (req: Request, res: Response) => {
|
||||
throw new Error("Failed to validate organization membership");
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getOrganizationPlan(organizationId);
|
||||
|
||||
if (plan.workspaceLimit !== null) {
|
||||
// case: limit imposed on number of workspaces allowed
|
||||
if (plan.workspacesUsed >= plan.workspaceLimit) {
|
||||
// case: number of workspaces used exceeds the number of workspaces allowed
|
||||
return res.status(400).send({
|
||||
message: 'Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (workspaceName.length < 1) {
|
||||
throw new Error("Workspace names must be at least 1-character long");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import {
|
||||
getHttpsEnabled
|
||||
} from '../../config';
|
||||
|
||||
// note: move this out
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
declare module 'jsonwebtoken' {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
userId: string;
|
||||
@@ -93,7 +97,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
const { email, clientProof } = req.body;
|
||||
const user = await User.findOne({
|
||||
email
|
||||
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
|
||||
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices');
|
||||
|
||||
if (!user) throw new Error('Failed to find user');
|
||||
|
||||
@@ -231,6 +235,8 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
import { validateUserEmail } from '../../validation';
|
||||
|
||||
/**
|
||||
* Send MFA token to email [email]
|
||||
* @param req
|
||||
|
||||
3519
backend/src/data/disposable_emails.txt
Normal file
3519
backend/src/data/disposable_emails.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,13 @@ const sendMail = async ({
|
||||
const temp = handlebars.compile(html);
|
||||
const htmlToSend = temp(substitutions);
|
||||
|
||||
await smtpTransporter.sendMail({
|
||||
const x = await smtpTransporter.sendMail({
|
||||
from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`,
|
||||
to: recipients.join(', '),
|
||||
subject: subjectLine,
|
||||
html: htmlToSend
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
|
||||
@@ -8,9 +8,6 @@ const apiLimiter = rateLimit({
|
||||
legacyHeaders: false,
|
||||
skip: (request) => {
|
||||
return request.path === '/healthcheck' || request.path === '/api/status'
|
||||
},
|
||||
keyGenerator: (req, res) => {
|
||||
return req.clientIp
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,10 +16,7 @@ const authLimit = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
return req.clientIp
|
||||
}
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
// 10 requests per hour
|
||||
@@ -30,10 +24,7 @@ const passwordLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000,
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
return req.clientIp
|
||||
}
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
const authLimiter = (req: any, res: any, next: any) => {
|
||||
|
||||
@@ -13,7 +13,6 @@ import swaggerUi = require('swagger-ui-express');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const swaggerFile = require('../spec.json');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const requestIp = require('request-ip');
|
||||
import { apiLimiter } from './helpers/rateLimiter';
|
||||
import {
|
||||
workspace as eeWorkspaceRouter,
|
||||
@@ -88,8 +87,6 @@ const main = async () => {
|
||||
})
|
||||
);
|
||||
|
||||
app.use(requestIp.mw());
|
||||
|
||||
if ((await getNodeEnv()) === 'production') {
|
||||
// enable app-wide rate-limiting + helmet security
|
||||
// in production
|
||||
|
||||
@@ -94,7 +94,8 @@ const userSchema = new Schema<IUser>(
|
||||
ip: String,
|
||||
userAgent: String
|
||||
}],
|
||||
default: []
|
||||
default: [],
|
||||
select: false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ router.post(
|
||||
router.post(
|
||||
'/mfa/send',
|
||||
authLimiter,
|
||||
body('email').isString().trim().notEmpty(),
|
||||
body('email').isString().trim().notEmpty().isEmail(),
|
||||
validateRequest,
|
||||
authController.sendMfaToken
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './user';
|
||||
export * from './workspace';
|
||||
export * from './bot';
|
||||
export * from './integration';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
@@ -8,7 +10,7 @@ import {
|
||||
} from '../models';
|
||||
import { validateMembership } from '../helpers/membership';
|
||||
import _ from 'lodash';
|
||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||
import { BadRequestError, UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
||||
import {
|
||||
validateMembershipOrg
|
||||
} from '../helpers/membershipOrg';
|
||||
@@ -17,6 +19,22 @@ import {
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from '../variables';
|
||||
|
||||
/**
|
||||
* Validate that email [email] is not disposable
|
||||
* @param email - email to validate
|
||||
*/
|
||||
export const validateUserEmail = (email: string) => {
|
||||
const emailDomain = email.split('@')[1];
|
||||
const disposableEmails = fs.readFileSync(
|
||||
path.resolve(__dirname, '../data/' + 'disposable_emails.txt'),
|
||||
'utf8'
|
||||
).split('\n');
|
||||
|
||||
if (disposableEmails.includes(emailDomain)) throw ValidationError({
|
||||
message: 'Failed to validate email as non-disposable'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that user (client) can access workspace
|
||||
* with id [workspaceId] and its environment [environment] with required permissions
|
||||
|
||||
Reference in New Issue
Block a user