mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #628 from Infisical/pentest-remediation
Fix issues/bugs
This commit is contained in:
1657
backend/package-lock.json
generated
1657
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,6 @@
|
||||
"posthog-node": "^2.6.0",
|
||||
"query-string": "^7.1.3",
|
||||
"rate-limit-mongo": "^2.3.2",
|
||||
"request-ip": "^3.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"stripe": "^10.7.0",
|
||||
"swagger-autogen": "^2.22.0",
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import { Request, Response } from 'express';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import * as bigintConversion from 'bigint-conversion';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const jsrp = require('jsrp');
|
||||
import { User, LoginSRPDetail } from '../../models';
|
||||
import {
|
||||
User,
|
||||
LoginSRPDetail,
|
||||
TokenVersion
|
||||
} from '../../models';
|
||||
import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth';
|
||||
import { checkUserDevice } from '../../helpers/user';
|
||||
import {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT
|
||||
ACTION_LOGOUT,
|
||||
AUTH_MODE_JWT
|
||||
} from '../../variables';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError
|
||||
} from '../../utils/errors';
|
||||
import { EELogService } from '../../ee/services';
|
||||
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
|
||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||
import {
|
||||
getJwtRefreshSecret,
|
||||
getJwtAuthLifetime,
|
||||
@@ -23,6 +33,7 @@ import {
|
||||
declare module 'jsonwebtoken' {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
userId: string;
|
||||
refreshVersion?: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +116,15 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.ip,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie('jid', tokens.refreshToken, {
|
||||
@@ -128,7 +143,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
ipAddress: req.ip
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
// return (access) token in response
|
||||
@@ -155,9 +170,9 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const logout = async (req: Request, res: Response) => {
|
||||
await clearTokens({
|
||||
userId: req.user._id.toString()
|
||||
});
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
|
||||
await clearTokens(req.authData.tokenVersionId)
|
||||
}
|
||||
|
||||
// clear httpOnly cookie
|
||||
res.cookie('jid', '', {
|
||||
@@ -176,7 +191,7 @@ export const logout = async (req: Request, res: Response) => {
|
||||
userId: req.user._id,
|
||||
actions: [logoutAction],
|
||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
ipAddress: req.ip
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
@@ -184,6 +199,30 @@ export const logout = async (req: Request, res: Response) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getCommonPasswords = async (req: Request, res: Response) => {
|
||||
const commonPasswords = fs.readFileSync(
|
||||
path.resolve(__dirname, '../../data/' + 'common_passwords.txt'),
|
||||
'utf8'
|
||||
).split('\n');
|
||||
|
||||
return res.status(200).send(commonPasswords);
|
||||
}
|
||||
|
||||
export const revokeAllSessions = async (req: Request, res: Response) => {
|
||||
await TokenVersion.updateMany({
|
||||
user: req.user._id
|
||||
}, {
|
||||
$inc: {
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully revoked all sessions.'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return user is authenticated
|
||||
* @param req
|
||||
@@ -197,7 +236,7 @@ export const checkAuth = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return new token by redeeming refresh token
|
||||
* Return new JWT access token by first validating the refresh token
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
@@ -206,7 +245,7 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
const refreshToken = req.cookies.jid;
|
||||
|
||||
if (!refreshToken) {
|
||||
throw new Error('Failed to find token in request cookies');
|
||||
throw new Error('Failed to find refresh token in request cookies');
|
||||
}
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
@@ -215,15 +254,27 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: decodedToken.userId
|
||||
}).select('+publicKey');
|
||||
}).select('+publicKey +refreshVersion +accessVersion');
|
||||
|
||||
if (!user) throw new Error('Failed to authenticate unfound user');
|
||||
if (!user?.publicKey)
|
||||
throw new Error('Failed to authenticate not fully set up account');
|
||||
|
||||
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
|
||||
|
||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||
message: 'Failed to validate refresh token'
|
||||
});
|
||||
|
||||
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
|
||||
message: 'Failed to validate refresh token'
|
||||
});
|
||||
|
||||
const token = createToken({
|
||||
payload: {
|
||||
userId: decodedToken.userId
|
||||
userId: decodedToken.userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
accessVersion: tokenVersion.refreshVersion
|
||||
},
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret()
|
||||
|
||||
@@ -6,8 +6,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
|
||||
@@ -96,6 +98,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
|
||||
@@ -134,6 +149,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,
|
||||
|
||||
@@ -3,12 +3,23 @@ import { Request, Response } from 'express';
|
||||
const jsrp = require('jsrp');
|
||||
import * as bigintConversion from 'bigint-conversion';
|
||||
import { User, BackupPrivateKey, LoginSRPDetail } from '../../models';
|
||||
import { createToken } from '../../helpers/auth';
|
||||
import { sendMail } from '../../helpers/nodemailer';
|
||||
import {
|
||||
createToken,
|
||||
sendMail,
|
||||
clearTokens
|
||||
} from '../../helpers';
|
||||
import { TokenService } from '../../services';
|
||||
import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables';
|
||||
import {
|
||||
TOKEN_EMAIL_PASSWORD_RESET,
|
||||
AUTH_MODE_JWT
|
||||
} from '../../variables';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../config';
|
||||
import {
|
||||
getSiteURL,
|
||||
getJwtSignupLifetime,
|
||||
getJwtSignupSecret,
|
||||
getHttpsEnabled
|
||||
} from '../../config';
|
||||
|
||||
/**
|
||||
* Password reset step 1: Send email verification link to email [email]
|
||||
@@ -18,14 +29,15 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../conf
|
||||
* @returns
|
||||
*/
|
||||
export const emailPasswordReset = async (req: Request, res: Response) => {
|
||||
const email = req.body.email;
|
||||
let email: string;
|
||||
email = req.body.email;
|
||||
|
||||
const user = await User.findOne({ email }).select('+publicKey');
|
||||
if (!user || !user?.publicKey) {
|
||||
// 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"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,7 +58,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"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,6 +110,7 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const srp1 = async (req: Request, res: Response) => {
|
||||
// return salt, serverPublicKey as part of first step of SRP protocol
|
||||
|
||||
const { clientPublicKey } = req.body;
|
||||
const user = await User.findOne({
|
||||
email: req.user.email
|
||||
@@ -127,7 +140,8 @@ export const srp1 = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change account SRP authentication information for user
|
||||
@@ -193,6 +207,19 @@ export const changePassword = async (req: Request, res: Response) => {
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
|
||||
await clearTokens(req.authData.tokenVersionId)
|
||||
}
|
||||
|
||||
// clear httpOnly cookie
|
||||
|
||||
res.cookie('jid', '', {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: (await getHttpsEnabled()) as boolean
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully changed password'
|
||||
|
||||
@@ -100,50 +100,52 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const pullSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
let secrets;
|
||||
let key;
|
||||
|
||||
// validate environment
|
||||
const workspaceEnvs = req.membership.workspace.environments;
|
||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||
throw new Error('Failed to validate environment');
|
||||
}
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
let secrets = await pull({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
environment,
|
||||
channel: channel ? channel : 'cli',
|
||||
ipAddress: req.ip
|
||||
});
|
||||
// validate environment
|
||||
const workspaceEnvs = req.membership.workspace.environments;
|
||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||
throw new Error('Failed to validate environment');
|
||||
}
|
||||
|
||||
const key = await Key.findOne({
|
||||
workspace: workspaceId,
|
||||
receiver: req.user._id
|
||||
})
|
||||
.sort({ createdAt: -1 })
|
||||
.populate('sender', '+publicKey');
|
||||
|
||||
if (channel !== 'cli') {
|
||||
// FIX: Fix this any
|
||||
secrets = reformatPullSecrets({ secrets }) as any;
|
||||
}
|
||||
secrets = await pull({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
environment,
|
||||
channel: channel ? channel : 'cli',
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
if (postHogClient) {
|
||||
// capture secrets pushed event in production
|
||||
postHogClient.capture({
|
||||
distinctId: req.user.email,
|
||||
event: 'secrets pulled',
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel ? channel : 'cli'
|
||||
}
|
||||
});
|
||||
}
|
||||
key = await Key.findOne({
|
||||
workspace: workspaceId,
|
||||
receiver: req.user._id
|
||||
})
|
||||
.sort({ createdAt: -1 })
|
||||
.populate('sender', '+publicKey');
|
||||
|
||||
if (channel !== 'cli') {
|
||||
secrets = reformatPullSecrets({ secrets });
|
||||
}
|
||||
|
||||
if (postHogClient) {
|
||||
// capture secrets pushed event in production
|
||||
postHogClient.capture({
|
||||
distinctId: req.user.email,
|
||||
event: 'secrets pulled',
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel ? channel : 'cli'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
@@ -160,48 +162,51 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
let secrets;
|
||||
let key;
|
||||
|
||||
// validate environment
|
||||
const workspaceEnvs = req.membership.workspace.environments;
|
||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||
throw new Error('Failed to validate environment');
|
||||
}
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const secrets = await pull({
|
||||
userId: req.serviceToken.user._id.toString(),
|
||||
workspaceId,
|
||||
environment,
|
||||
channel: 'cli',
|
||||
ipAddress: req.ip
|
||||
});
|
||||
// validate environment
|
||||
const workspaceEnvs = req.membership.workspace.environments;
|
||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||
throw new Error('Failed to validate environment');
|
||||
}
|
||||
|
||||
const key = {
|
||||
encryptedKey: req.serviceToken.encryptedKey,
|
||||
nonce: req.serviceToken.nonce,
|
||||
sender: {
|
||||
publicKey: req.serviceToken.publicKey
|
||||
},
|
||||
receiver: req.serviceToken.user,
|
||||
workspace: req.serviceToken.workspace
|
||||
};
|
||||
secrets = await pull({
|
||||
userId: req.serviceToken.user._id.toString(),
|
||||
workspaceId,
|
||||
environment,
|
||||
channel: 'cli',
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
if (postHogClient) {
|
||||
// capture secrets pulled event in production
|
||||
postHogClient.capture({
|
||||
distinctId: req.serviceToken.user.email,
|
||||
event: 'secrets pulled',
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel ? channel : 'cli'
|
||||
}
|
||||
});
|
||||
}
|
||||
key = {
|
||||
encryptedKey: req.serviceToken.encryptedKey,
|
||||
nonce: req.serviceToken.nonce,
|
||||
sender: {
|
||||
publicKey: req.serviceToken.publicKey
|
||||
},
|
||||
receiver: req.serviceToken.user,
|
||||
workspace: req.serviceToken.workspace
|
||||
};
|
||||
|
||||
if (postHogClient) {
|
||||
// capture secrets pulled event in production
|
||||
postHogClient.capture({
|
||||
distinctId: req.serviceToken.user.email,
|
||||
event: 'secrets pulled',
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel ? channel : 'cli'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: reformatPullSecrets({ secrets }),
|
||||
|
||||
@@ -7,6 +7,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
|
||||
@@ -16,7 +17,11 @@ import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpC
|
||||
* @returns
|
||||
*/
|
||||
export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||
const email = req.body.email;
|
||||
let email: string;
|
||||
email = req.body.email;
|
||||
|
||||
// validate that email is not disposable
|
||||
validateUserEmail(email);
|
||||
|
||||
const user = await User.findOne({ email }).select('+publicKey');
|
||||
if (user && user?.publicKey) {
|
||||
|
||||
@@ -13,6 +13,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";
|
||||
|
||||
@@ -115,6 +116,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");
|
||||
}
|
||||
|
||||
@@ -83,7 +83,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');
|
||||
|
||||
@@ -105,7 +105,6 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
|
||||
// compare server and client shared keys
|
||||
if (server.checkClientProof(clientProof)) {
|
||||
|
||||
if (user.isMfaEnabled) {
|
||||
// case: user has MFA enabled
|
||||
|
||||
@@ -141,12 +140,16 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.ip,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie('jid', tokens.refreshToken, {
|
||||
@@ -156,7 +159,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
|
||||
// case: user does not have MFA enablgged
|
||||
// case: user does not have MFA enabled
|
||||
// return (access) token in response
|
||||
|
||||
interface ResponseData {
|
||||
@@ -267,12 +270,16 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
||||
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.ip,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie('jid', tokens.refreshToken, {
|
||||
@@ -330,7 +337,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
ipAddress: req.ip
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
return res.status(200).send(resObj);
|
||||
|
||||
@@ -297,7 +297,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions,
|
||||
channel,
|
||||
ipAddress: req.ip,
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions: [addAction],
|
||||
channel,
|
||||
ipAddress: req.ip,
|
||||
ipAddress: req.realIP,
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
@@ -785,7 +785,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress: req.ip,
|
||||
ipAddress: req.realIP,
|
||||
}));
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@@ -1020,7 +1020,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
ipAddress: req.ip,
|
||||
ipAddress: req.realIP,
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
@@ -1158,7 +1158,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [deleteAction],
|
||||
channel,
|
||||
ipAddress: req.ip,
|
||||
ipAddress: req.realIP,
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
|
||||
@@ -114,7 +114,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id.toString()
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
token = tokens.token;
|
||||
@@ -237,7 +239,9 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id.toString()
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
token = tokens.token;
|
||||
|
||||
@@ -68,7 +68,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
secrets,
|
||||
channel: channel ? channel : 'cli',
|
||||
ipAddress: req.ip
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
await pushKeys({
|
||||
@@ -111,6 +111,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const pullSecrets = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
@@ -128,17 +129,16 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
throw new Error('Failed to validate environment');
|
||||
}
|
||||
|
||||
let secrets = await pull({
|
||||
secrets = await pull({
|
||||
userId,
|
||||
workspaceId,
|
||||
environment,
|
||||
channel: channel ? channel : 'cli',
|
||||
ipAddress: req.ip
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
if (channel !== 'cli') {
|
||||
// FIX: Fix this any
|
||||
secrets = reformatPullSecrets({ secrets }) as any;
|
||||
secrets = reformatPullSecrets({ secrets });
|
||||
}
|
||||
|
||||
if (postHogClient) {
|
||||
|
||||
@@ -113,7 +113,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
|
||||
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');
|
||||
|
||||
@@ -179,12 +179,16 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.ip,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie('jid', tokens.refreshToken, {
|
||||
@@ -239,7 +243,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
ipAddress: req.ip
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
return res.status(200).send(response);
|
||||
|
||||
@@ -137,7 +137,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id.toString()
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
token = tokens.token;
|
||||
|
||||
1497
backend/src/data/common_passwords.txt
Normal file
1497
backend/src/data/common_passwords.txt
Normal file
File diff suppressed because it is too large
Load Diff
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
@@ -6,7 +6,9 @@ import {
|
||||
User,
|
||||
ServiceTokenData,
|
||||
ServiceAccount,
|
||||
APIKeyData
|
||||
APIKeyData,
|
||||
TokenVersion,
|
||||
ITokenVersion
|
||||
} from '../models';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
@@ -35,7 +37,7 @@ import {
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.headers - HTTP request headers object
|
||||
*/
|
||||
const validateAuthMode = ({
|
||||
export const validateAuthMode = ({
|
||||
headers,
|
||||
acceptedAuthModes
|
||||
}: {
|
||||
@@ -97,7 +99,7 @@ const validateAuthMode = ({
|
||||
* @param {String} obj.authTokenValue - JWT token value
|
||||
* @returns {User} user - user corresponding to JWT token
|
||||
*/
|
||||
const getAuthUserPayload = async ({
|
||||
export const getAuthUserPayload = async ({
|
||||
authTokenValue
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
@@ -107,14 +109,32 @@ const getAuthUserPayload = async ({
|
||||
);
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: decodedToken.userId
|
||||
}).select('+publicKey');
|
||||
_id: new Types.ObjectId(decodedToken.userId)
|
||||
}).select('+publicKey +accessVersion');
|
||||
|
||||
if (!user) throw AccountNotFoundError({ message: 'Failed to find User' });
|
||||
if (!user) throw AccountNotFoundError({ message: 'Failed to find user' });
|
||||
|
||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate User with partially set up account' });
|
||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate user with partially set up account' });
|
||||
|
||||
return user;
|
||||
const tokenVersion = await TokenVersion.findOneAndUpdate({
|
||||
_id: new Types.ObjectId(decodedToken.tokenVersionId),
|
||||
user: user._id
|
||||
}, {
|
||||
lastUsed: new Date()
|
||||
});
|
||||
|
||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||
message: 'Failed to validate access token'
|
||||
});
|
||||
|
||||
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
|
||||
message: 'Failed to validate access token'
|
||||
});
|
||||
|
||||
return ({
|
||||
user,
|
||||
tokenVersionId: tokenVersion._id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +143,7 @@ const getAuthUserPayload = async ({
|
||||
* @param {String} obj.authTokenValue - service token value
|
||||
* @returns {ServiceTokenData} serviceTokenData - service token data
|
||||
*/
|
||||
const getAuthSTDPayload = async ({
|
||||
export const getAuthSTDPayload = async ({
|
||||
authTokenValue
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
@@ -169,7 +189,7 @@ const getAuthSTDPayload = async ({
|
||||
* @param {String} obj.authTokenValue - service account access token value
|
||||
* @returns {ServiceAccount} serviceAccount
|
||||
*/
|
||||
const getAuthSAAKPayload = async ({
|
||||
export const getAuthSAAKPayload = async ({
|
||||
authTokenValue
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
@@ -198,7 +218,7 @@ const getAuthSAAKPayload = async ({
|
||||
* @param {String} obj.authTokenValue - API key value
|
||||
* @returns {APIKeyData} apiKeyData - API key data
|
||||
*/
|
||||
const getAuthAPIKeyPayload = async ({
|
||||
export const getAuthAPIKeyPayload = async ({
|
||||
authTokenValue
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
@@ -255,12 +275,43 @@ const getAuthAPIKeyPayload = async ({
|
||||
* @return {String} obj.token - issued JWT token
|
||||
* @return {String} obj.refreshToken - issued refresh token
|
||||
*/
|
||||
const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
||||
export const issueAuthTokens = async ({
|
||||
userId,
|
||||
ip,
|
||||
userAgent
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
}) => {
|
||||
let tokenVersion: ITokenVersion | null;
|
||||
|
||||
// continue with (session) token version matching existing ip and user agent
|
||||
tokenVersion = await TokenVersion.findOne({
|
||||
user: userId,
|
||||
ip,
|
||||
userAgent
|
||||
});
|
||||
|
||||
if (!tokenVersion) {
|
||||
// case: no existing ip and user agent exists
|
||||
// -> create new (session) token version for ip and user agent
|
||||
tokenVersion = await new TokenVersion({
|
||||
user: userId,
|
||||
refreshVersion: 0,
|
||||
accessVersion: 0,
|
||||
ip,
|
||||
userAgent,
|
||||
lastUsed: new Date()
|
||||
}).save();
|
||||
}
|
||||
|
||||
// issue tokens
|
||||
const token = createToken({
|
||||
payload: {
|
||||
userId
|
||||
userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
accessVersion: tokenVersion.accessVersion
|
||||
},
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret()
|
||||
@@ -268,7 +319,9 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
||||
|
||||
const refreshToken = createToken({
|
||||
payload: {
|
||||
userId
|
||||
userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
refreshVersion: tokenVersion.refreshVersion
|
||||
},
|
||||
expiresIn: await getJwtRefreshLifetime(),
|
||||
secret: await getJwtRefreshSecret()
|
||||
@@ -285,13 +338,15 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.userId - id of user whose tokens are cleared.
|
||||
*/
|
||||
const clearTokens = async ({ userId }: { userId: string }): Promise<void> => {
|
||||
export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void> => {
|
||||
// increment refreshVersion on user by 1
|
||||
User.findOneAndUpdate({
|
||||
_id: userId
|
||||
|
||||
await TokenVersion.findOneAndUpdate({
|
||||
_id: tokenVersionId
|
||||
}, {
|
||||
$inc: {
|
||||
refreshVersion: 1
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -304,7 +359,7 @@ const clearTokens = async ({ userId }: { userId: string }): Promise<void> => {
|
||||
* @param {String} obj.secret - (JWT) secret such as [JWT_AUTH_SECRET]
|
||||
* @param {String} obj.expiresIn - string describing time span such as '10h' or '7d'
|
||||
*/
|
||||
const createToken = ({
|
||||
export const createToken = ({
|
||||
payload,
|
||||
expiresIn,
|
||||
secret
|
||||
@@ -318,7 +373,7 @@ const createToken = ({
|
||||
});
|
||||
};
|
||||
|
||||
const validateProviderAuthToken = async ({
|
||||
export const validateProviderAuthToken = async ({
|
||||
email,
|
||||
user,
|
||||
providerAuthToken,
|
||||
@@ -341,16 +396,4 @@ const validateProviderAuthToken = async ({
|
||||
) {
|
||||
throw new Error('Invalid authentication credentials.')
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
validateAuthMode,
|
||||
validateProviderAuthToken,
|
||||
getAuthUserPayload,
|
||||
getAuthSTDPayload,
|
||||
getAuthSAAKPayload,
|
||||
getAuthAPIKeyPayload,
|
||||
createToken,
|
||||
issueAuthTokens,
|
||||
clearTokens
|
||||
};
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import { InternalServerError } from "../utils/errors";
|
||||
* @param {String} obj.name - name of bot
|
||||
* @param {String} obj.workspaceId - id of workspace that bot belongs to
|
||||
*/
|
||||
const createBot = async ({
|
||||
export const createBot = async ({
|
||||
name,
|
||||
workspaceId,
|
||||
}: {
|
||||
@@ -93,7 +93,7 @@ const createBot = async ({
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - environment
|
||||
*/
|
||||
const getSecretsHelper = async ({
|
||||
export const getSecretsBotHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
}: {
|
||||
@@ -136,7 +136,7 @@ const getSecretsHelper = async ({
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @returns {String} key - decrypted workspace key
|
||||
*/
|
||||
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
export const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
@@ -194,7 +194,7 @@ const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
* @param {String} obj1.workspaceId - id of workspace
|
||||
* @param {String} obj1.plaintext - plaintext to encrypt
|
||||
*/
|
||||
const encryptSymmetricHelper = async ({
|
||||
export const encryptSymmetricHelper = async ({
|
||||
workspaceId,
|
||||
plaintext,
|
||||
}: {
|
||||
@@ -222,7 +222,7 @@ const encryptSymmetricHelper = async ({
|
||||
* @param {String} obj.iv - iv
|
||||
* @param {String} obj.tag - tag
|
||||
*/
|
||||
const decryptSymmetricHelper = async ({
|
||||
export const decryptSymmetricHelper = async ({
|
||||
workspaceId,
|
||||
ciphertext,
|
||||
iv,
|
||||
@@ -242,11 +242,4 @@ const decryptSymmetricHelper = async ({
|
||||
});
|
||||
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
export {
|
||||
createBot,
|
||||
getSecretsHelper,
|
||||
encryptSymmetricHelper,
|
||||
decryptSymmetricHelper
|
||||
};
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import { getLogger } from '../utils/logger';
|
||||
* @param {String} obj.mongoURL - mongo connection string
|
||||
* @returns
|
||||
*/
|
||||
const initDatabaseHelper = async ({
|
||||
export const initDatabaseHelper = async ({
|
||||
mongoURL
|
||||
}: {
|
||||
mongoURL: string;
|
||||
@@ -30,7 +30,7 @@ const initDatabaseHelper = async ({
|
||||
/**
|
||||
* Close database conection
|
||||
*/
|
||||
const closeDatabaseHelper = async () => {
|
||||
export const closeDatabaseHelper = async () => {
|
||||
return Promise.all([
|
||||
new Promise((resolve) => {
|
||||
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
||||
@@ -41,9 +41,4 @@ const closeDatabaseHelper = async () => {
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
export {
|
||||
initDatabaseHelper,
|
||||
closeDatabaseHelper
|
||||
}
|
||||
@@ -18,7 +18,7 @@ interface Event {
|
||||
* @param {String} obj.event.workspaceId - id of workspace that event is part of
|
||||
* @param {Object} obj.event.payload - payload of event (depends on event)
|
||||
*/
|
||||
const handleEventHelper = async ({ event }: { event: Event }) => {
|
||||
export const handleEventHelper = async ({ event }: { event: Event }) => {
|
||||
const { workspaceId, environment } = event;
|
||||
|
||||
// TODO: moduralize bot check into separate function
|
||||
@@ -37,6 +37,4 @@ const handleEventHelper = async ({ event }: { event: Event }) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export { handleEventHelper };
|
||||
};
|
||||
17
backend/src/helpers/index.ts
Normal file
17
backend/src/helpers/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export * from './auth';
|
||||
export * from './bot';
|
||||
export * from './database';
|
||||
export * from './event';
|
||||
export * from './integration';
|
||||
export * from './key';
|
||||
export * from './membership';
|
||||
export * from './membershipOrg';
|
||||
export * from './nodemailer';
|
||||
export * from './organization';
|
||||
export * from './rateLimiter';
|
||||
export * from './secret';
|
||||
export * from './secrets';
|
||||
export * from './signup';
|
||||
export * from './token';
|
||||
export * from './user';
|
||||
export * from './workspace';
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import {
|
||||
UnauthorizedRequestError,
|
||||
} from '../utils/errors';
|
||||
import RequestError from '../utils/requestError';
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
@@ -36,7 +35,7 @@ interface Update {
|
||||
* @param {String} obj.code - code
|
||||
* @returns {IntegrationAuth} integrationAuth - integration auth after OAuth2 code-token exchange
|
||||
*/
|
||||
const handleOAuthExchangeHelper = async ({
|
||||
export const handleOAuthExchangeHelper = async ({
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
@@ -110,7 +109,7 @@ const handleOAuthExchangeHelper = async ({
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.workspaceId - id of workspace
|
||||
*/
|
||||
const syncIntegrationsHelper = async ({
|
||||
export const syncIntegrationsHelper = async ({
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
@@ -162,7 +161,7 @@ const syncIntegrationsHelper = async ({
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} refreshToken - decrypted refresh token
|
||||
*/
|
||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
export const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
const integrationAuth = await IntegrationAuth
|
||||
.findById(integrationAuthId)
|
||||
.select('+refreshCiphertext +refreshIV +refreshTag');
|
||||
@@ -187,7 +186,7 @@ const syncIntegrationsHelper = async ({
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @returns {String} accessToken - decrypted access token
|
||||
*/
|
||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
export const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||
let accessId;
|
||||
let accessToken;
|
||||
const integrationAuth = await IntegrationAuth
|
||||
@@ -240,7 +239,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
||||
* @param {String} obj.integrationAuthId - id of integration auth
|
||||
* @param {String} obj.refreshToken - refresh token
|
||||
*/
|
||||
const setIntegrationAuthRefreshHelper = async ({
|
||||
export const setIntegrationAuthRefreshHelper = async ({
|
||||
integrationAuthId,
|
||||
refreshToken
|
||||
}: {
|
||||
@@ -282,7 +281,7 @@ const setIntegrationAuthRefreshHelper = async ({
|
||||
* @param {String} obj.accessToken - access token
|
||||
* @param {Date} obj.accessExpiresAt - expiration date of access token
|
||||
*/
|
||||
const setIntegrationAuthAccessHelper = async ({
|
||||
export const setIntegrationAuthAccessHelper = async ({
|
||||
integrationAuthId,
|
||||
accessId,
|
||||
accessToken,
|
||||
@@ -327,13 +326,4 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
});
|
||||
|
||||
return integrationAuth;
|
||||
}
|
||||
|
||||
export {
|
||||
handleOAuthExchangeHelper,
|
||||
syncIntegrationsHelper,
|
||||
getIntegrationAuthRefreshHelper,
|
||||
getIntegrationAuthAccessHelper,
|
||||
setIntegrationAuthRefreshHelper,
|
||||
setIntegrationAuthAccessHelper
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ interface Key {
|
||||
* @param {String} obj.keys.nonce - nonce for encryption
|
||||
* @param {String} obj.keys.userId - id of receiver user
|
||||
*/
|
||||
const pushKeys = async ({
|
||||
export const pushKeys = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
keys
|
||||
@@ -50,6 +50,4 @@ const pushKeys = async ({
|
||||
workspace: workspaceId
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
export { pushKeys };
|
||||
};
|
||||
@@ -10,10 +10,10 @@ import { MembershipNotFoundError, BadRequestError } from "../utils/errors";
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @returns {Membership} membership - membership of user with id [userId] for workspace with id [workspaceId]
|
||||
*/
|
||||
const validateMembership = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
acceptedRoles,
|
||||
export const validateMembership = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
acceptedRoles,
|
||||
}: {
|
||||
userId: Types.ObjectId | string;
|
||||
workspaceId: Types.ObjectId | string;
|
||||
@@ -46,8 +46,8 @@ const validateMembership = async ({
|
||||
* @param {Object} queryObj - query object
|
||||
* @return {Object} membership - membership
|
||||
*/
|
||||
const findMembership = async (queryObj: any) => {
|
||||
const membership = await Membership.findOne(queryObj);
|
||||
export const findMembership = async (queryObj: any) => {
|
||||
const membership = await Membership.findOne(queryObj);
|
||||
return membership;
|
||||
};
|
||||
|
||||
@@ -59,10 +59,10 @@ const findMembership = async (queryObj: any) => {
|
||||
* @param {String} obj.workspaceId - id of workspace.
|
||||
* @param {String[]} obj.roles - roles of users.
|
||||
*/
|
||||
const addMemberships = async ({
|
||||
userIds,
|
||||
workspaceId,
|
||||
roles,
|
||||
export const addMemberships = async ({
|
||||
userIds,
|
||||
workspaceId,
|
||||
roles
|
||||
}: {
|
||||
userIds: string[];
|
||||
workspaceId: string;
|
||||
@@ -93,9 +93,9 @@ const addMemberships = async ({
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.membershipId - id of membership to delete
|
||||
*/
|
||||
const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||
const deletedMembership = await Membership.findOneAndDelete({
|
||||
_id: membershipId,
|
||||
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||
const deletedMembership = await Membership.findOneAndDelete({
|
||||
_id: membershipId
|
||||
});
|
||||
|
||||
// delete keys associated with the membership
|
||||
@@ -107,7 +107,5 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||
});
|
||||
}
|
||||
|
||||
return deletedMembership;
|
||||
return deletedMembership;
|
||||
};
|
||||
|
||||
export { validateMembership, addMemberships, findMembership, deleteMembership };
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
* @param {Types.ObjectId} obj.organizationId
|
||||
* @param {String[]} obj.acceptedRoles
|
||||
*/
|
||||
const validateMembershipOrg = async ({
|
||||
export const validateMembershipOrg = async ({
|
||||
userId,
|
||||
organizationId,
|
||||
acceptedRoles,
|
||||
@@ -59,7 +59,7 @@ const validateMembershipOrg = async ({
|
||||
* @param {Object} queryObj - query object
|
||||
* @return {Object} membershipOrg - membership
|
||||
*/
|
||||
const findMembershipOrg = (queryObj: any) => {
|
||||
export const findMembershipOrg = (queryObj: any) => {
|
||||
const membershipOrg = MembershipOrg.findOne(queryObj);
|
||||
return membershipOrg;
|
||||
};
|
||||
@@ -72,7 +72,7 @@ const findMembershipOrg = (queryObj: any) => {
|
||||
* @param {String} obj.organizationId - id of organization.
|
||||
* @param {String[]} obj.roles - roles of users.
|
||||
*/
|
||||
const addMembershipsOrg = async ({
|
||||
export const addMembershipsOrg = async ({
|
||||
userIds,
|
||||
organizationId,
|
||||
roles,
|
||||
@@ -111,7 +111,7 @@ const addMembershipsOrg = async ({
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.membershipOrgId - id of organization membership to delete
|
||||
*/
|
||||
const deleteMembershipOrg = async ({
|
||||
export const deleteMembershipOrg = async ({
|
||||
membershipOrgId
|
||||
}: {
|
||||
membershipOrgId: string;
|
||||
@@ -148,11 +148,4 @@ const deleteMembershipOrg = async ({
|
||||
}
|
||||
|
||||
return deletedMembershipOrg;
|
||||
};
|
||||
|
||||
export {
|
||||
validateMembershipOrg,
|
||||
findMembershipOrg,
|
||||
addMembershipsOrg,
|
||||
deleteMembershipOrg
|
||||
};
|
||||
};
|
||||
@@ -13,7 +13,7 @@ let smtpTransporter: nodemailer.Transporter;
|
||||
* @param {String[]} obj.recipients - email addresses of people to send email to
|
||||
* @param {Object} obj.substitutions - object containing template substitutions
|
||||
*/
|
||||
const sendMail = async ({
|
||||
export const sendMail = async ({
|
||||
template,
|
||||
subjectLine,
|
||||
recipients,
|
||||
@@ -41,8 +41,6 @@ const sendMail = async ({
|
||||
}
|
||||
};
|
||||
|
||||
const setTransporter = (transporter: nodemailer.Transporter) => {
|
||||
export const setTransporter = (transporter: nodemailer.Transporter) => {
|
||||
smtpTransporter = transporter;
|
||||
};
|
||||
|
||||
export { sendMail, setTransporter };
|
||||
};
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
* @param {String} obj.email - POC email that will receive invoice info
|
||||
* @param {Object} organization - new organization
|
||||
*/
|
||||
const createOrganization = async ({
|
||||
export const createOrganization = async ({
|
||||
name,
|
||||
email,
|
||||
}: {
|
||||
@@ -70,7 +70,7 @@ const createOrganization = async ({
|
||||
* @return {Object} obj.stripeSubscription - new stripe subscription
|
||||
* @return {Subscription} obj.subscription - new subscription
|
||||
*/
|
||||
const initSubscriptionOrg = async ({
|
||||
export const initSubscriptionOrg = async ({
|
||||
organizationId,
|
||||
}: {
|
||||
organizationId: Types.ObjectId;
|
||||
@@ -125,7 +125,7 @@ const initSubscriptionOrg = async ({
|
||||
* @param {Object} obj
|
||||
* @param {Number} obj.organizationId - id of subscription's organization
|
||||
*/
|
||||
const updateSubscriptionOrgQuantity = async ({
|
||||
export const updateSubscriptionOrgQuantity = async ({
|
||||
organizationId,
|
||||
}: {
|
||||
organizationId: string;
|
||||
@@ -171,10 +171,4 @@ const updateSubscriptionOrgQuantity = async ({
|
||||
}
|
||||
|
||||
return stripeSubscription;
|
||||
};
|
||||
|
||||
export {
|
||||
createOrganization,
|
||||
initSubscriptionOrg,
|
||||
updateSubscriptionOrgQuantity
|
||||
};
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import rateLimit from 'express-rate-limit';
|
||||
const MongoStore = require('rate-limit-mongo');
|
||||
|
||||
// 200 per minute
|
||||
const apiLimiter = rateLimit({
|
||||
export const apiLimiter = rateLimit({
|
||||
store: new MongoStore({
|
||||
uri: process.env.MONGO_URL,
|
||||
expireTimeMs: 1000 * 60,
|
||||
@@ -17,7 +17,7 @@ const apiLimiter = rateLimit({
|
||||
return request.path === '/healthcheck' || request.path === '/api/status'
|
||||
},
|
||||
keyGenerator: (req, res) => {
|
||||
return req.clientIp
|
||||
return req.realIP
|
||||
}
|
||||
});
|
||||
|
||||
@@ -34,12 +34,12 @@ const authLimit = rateLimit({
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
return req.clientIp
|
||||
return req.realIP
|
||||
}
|
||||
});
|
||||
|
||||
// 50 requests per 1 hour
|
||||
const passwordLimiter = rateLimit({
|
||||
export const passwordLimiter = rateLimit({
|
||||
store: new MongoStore({
|
||||
uri: process.env.MONGO_URL,
|
||||
expireTimeMs: 1000 * 60 * 60,
|
||||
@@ -51,20 +51,14 @@ const passwordLimiter = rateLimit({
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
return req.clientIp
|
||||
return req.realIP
|
||||
}
|
||||
});
|
||||
|
||||
const authLimiter = (req: any, res: any, next: any) => {
|
||||
export const authLimiter = (req: any, res: any, next: any) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
authLimit(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
apiLimiter,
|
||||
authLimiter,
|
||||
passwordLimiter
|
||||
};
|
||||
};
|
||||
@@ -60,7 +60,7 @@ interface Update {
|
||||
* @param {String} obj.environment - environment for secrets
|
||||
* @param {Object[]} obj.secrets - secrets to push
|
||||
*/
|
||||
const v1PushSecrets = async ({
|
||||
export const v1PushSecrets = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -304,7 +304,7 @@ const v1PushSecrets = async ({
|
||||
* @param {String} obj.channel - channel (web/cli/auto)
|
||||
* @param {String} obj.ipAddress - ip address of request to push secrets
|
||||
*/
|
||||
const v2PushSecrets = async ({
|
||||
export const v2PushSecrets = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -530,7 +530,7 @@ const v2PushSecrets = async ({
|
||||
* @param {String} obj.workspaceId - id of workspace to pull from
|
||||
* @param {String} obj.environment - environment for secrets
|
||||
*/
|
||||
const getSecrets = async ({
|
||||
export const getSecrets = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -570,7 +570,7 @@ const getSecrets = async ({
|
||||
* @param {String} obj.channel - channel (web/cli/auto)
|
||||
* @param {String} obj.ipAddress - ip address of request to push secrets
|
||||
*/
|
||||
const pullSecrets = async ({
|
||||
export const pullSecrets = async ({
|
||||
userId,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -614,7 +614,7 @@ const pullSecrets = async ({
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.secrets
|
||||
*/
|
||||
const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
||||
export const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
||||
const reformatedSecrets = secrets.map((s) => ({
|
||||
_id: s._id,
|
||||
workspace: s.workspace,
|
||||
@@ -644,6 +644,4 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
||||
}));
|
||||
|
||||
return reformatedSecrets;
|
||||
};
|
||||
|
||||
export { v1PushSecrets, v2PushSecrets, pullSecrets, reformatPullSecrets };
|
||||
};
|
||||
@@ -45,8 +45,8 @@ import {
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
const createSecretBlindIndexDataHelper = async ({
|
||||
workspaceId,
|
||||
export const createSecretBlindIndexDataHelper = async ({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
@@ -98,8 +98,8 @@ const createSecretBlindIndexDataHelper = async ({
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
||||
* @returns
|
||||
*/
|
||||
const getSecretBlindIndexSaltHelper = async ({
|
||||
workspaceId,
|
||||
export const getSecretBlindIndexSaltHelper = async ({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
@@ -147,9 +147,9 @@ const getSecretBlindIndexSaltHelper = async ({
|
||||
* @param {String} obj.secretName - name of secret to generate blind index for
|
||||
* @param {String} obj.salt - base64-salt
|
||||
*/
|
||||
const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
secretName,
|
||||
salt,
|
||||
export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
secretName,
|
||||
salt
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
@@ -177,9 +177,9 @@ const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
* @param {Stringj} obj.secretName - name of secret to generate blind index for
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
*/
|
||||
const generateSecretBlindIndexHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
export const generateSecretBlindIndexHelper = async ({
|
||||
secretName,
|
||||
workspaceId
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
@@ -247,7 +247,7 @@ const generateSecretBlindIndexHelper = async ({
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const createSecretHelper = async ({
|
||||
export const createSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -397,10 +397,10 @@ const createSecretHelper = async ({
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
authData,
|
||||
export const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
authData
|
||||
}: GetSecretsParams) => {
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
@@ -472,7 +472,7 @@ const getSecretsHelper = async ({
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const getSecretHelper = async ({
|
||||
export const getSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -558,7 +558,8 @@ const getSecretHelper = async ({
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const updateSecretHelper = async ({
|
||||
|
||||
export const updateSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -698,7 +699,7 @@ const updateSecretHelper = async ({
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const deleteSecretHelper = async ({
|
||||
export const deleteSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
@@ -799,16 +800,4 @@ const deleteSecretHelper = async ({
|
||||
secrets,
|
||||
secret,
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
generateSecretBlindIndexHelper,
|
||||
createSecretHelper,
|
||||
getSecretsHelper,
|
||||
getSecretHelper,
|
||||
updateSecretHelper,
|
||||
deleteSecretHelper,
|
||||
};
|
||||
};
|
||||
@@ -13,11 +13,11 @@ import { TOKEN_EMAIL_CONFIRMATION } from '../variables';
|
||||
* @param {String} obj.email - email
|
||||
* @returns {Boolean} success - whether or not operation was successful
|
||||
*/
|
||||
const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||
const token = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_CONFIRMATION,
|
||||
email
|
||||
});
|
||||
export const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||
const token = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_CONFIRMATION,
|
||||
email
|
||||
});
|
||||
|
||||
// send mail
|
||||
await sendMail({
|
||||
@@ -36,7 +36,7 @@ const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||
* @param {String} obj.email - emai
|
||||
* @param {String} obj.code - code that was sent to [email]
|
||||
*/
|
||||
const checkEmailVerification = async ({
|
||||
export const checkEmailVerification = async ({
|
||||
email,
|
||||
code
|
||||
}: {
|
||||
@@ -57,7 +57,7 @@ const checkEmailVerification = async ({
|
||||
* @param {String} obj.organizationName - name of organization to initialize
|
||||
* @param {IUser} obj.user - user who we are initializing for
|
||||
*/
|
||||
const initializeDefaultOrg = async ({
|
||||
export const initializeDefaultOrg = async ({
|
||||
organizationName,
|
||||
user
|
||||
}: {
|
||||
@@ -81,6 +81,4 @@ const initializeDefaultOrg = async ({
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
|
||||
}
|
||||
};
|
||||
|
||||
export { sendEmailVerification, checkEmailVerification, initializeDefaultOrg };
|
||||
};
|
||||
@@ -20,7 +20,7 @@ import { getSaltRounds } from "../config";
|
||||
* @param {Types.ObjectId} obj.organizationId
|
||||
* @returns {String} token - the created token
|
||||
*/
|
||||
const createTokenHelper = async ({
|
||||
export const createTokenHelper = async ({
|
||||
type,
|
||||
email,
|
||||
phoneNumber,
|
||||
@@ -121,7 +121,7 @@ const createTokenHelper = async ({
|
||||
* @param {String} obj.email - email associated with the token
|
||||
* @param {String} obj.token - value of the token
|
||||
*/
|
||||
const validateTokenHelper = async ({
|
||||
export const validateTokenHelper = async ({
|
||||
type,
|
||||
email,
|
||||
phoneNumber,
|
||||
@@ -212,6 +212,4 @@ const validateTokenHelper = async ({
|
||||
|
||||
// case: token is valid
|
||||
await TokenData.findByIdAndDelete(tokenData._id);
|
||||
};
|
||||
|
||||
export { createTokenHelper, validateTokenHelper };
|
||||
};
|
||||
@@ -15,7 +15,7 @@ import { SecretService } from '../services';
|
||||
* @param {String} organizationId - id of organization to create workspace in
|
||||
* @param {Object} workspace - new workspace
|
||||
*/
|
||||
const createWorkspace = async ({
|
||||
export const createWorkspace = async ({
|
||||
name,
|
||||
organizationId
|
||||
}: {
|
||||
@@ -50,23 +50,18 @@ const createWorkspace = async ({
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.id - id of workspace to delete
|
||||
*/
|
||||
const deleteWorkspace = async ({ id }: { id: string }) => {
|
||||
await Workspace.deleteOne({ _id: id });
|
||||
await Bot.deleteOne({
|
||||
workspace: id
|
||||
});
|
||||
await Membership.deleteMany({
|
||||
workspace: id
|
||||
});
|
||||
await Secret.deleteMany({
|
||||
workspace: id
|
||||
});
|
||||
await Key.deleteMany({
|
||||
workspace: id
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
createWorkspace,
|
||||
deleteWorkspace
|
||||
export const deleteWorkspace = async ({ id }: { id: string }) => {
|
||||
await Workspace.deleteOne({ _id: id });
|
||||
await Bot.deleteOne({
|
||||
workspace: id
|
||||
});
|
||||
await Membership.deleteMany({
|
||||
workspace: id
|
||||
});
|
||||
await Secret.deleteMany({
|
||||
workspace: id
|
||||
});
|
||||
await Key.deleteMany({
|
||||
workspace: id
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
@@ -86,8 +85,6 @@ const main = async () => {
|
||||
})
|
||||
);
|
||||
|
||||
app.use(requestIp.mw());
|
||||
|
||||
if ((await getNodeEnv()) === "production") {
|
||||
// enable app-wide rate-limiting + helmet security
|
||||
// in production
|
||||
@@ -96,6 +93,13 @@ const main = async () => {
|
||||
app.use(helmet());
|
||||
}
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// default to IP address provided by Cloudflare
|
||||
const cfIp = req.headers['cf-connecting-ip'];
|
||||
req.realIP = Array.isArray(cfIp) ? cfIp[0] : (cfIp as string) || req.ip;
|
||||
next();
|
||||
});
|
||||
|
||||
// (EE) routes
|
||||
app.use("/api/v1/secret", eeSecretRouter);
|
||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
@@ -10,4 +11,5 @@ export interface AuthData {
|
||||
authChannel: string;
|
||||
authIP: string;
|
||||
authUserAgent: string;
|
||||
tokenVersionId?: Types.ObjectId;
|
||||
}
|
||||
@@ -71,10 +71,12 @@ const requireAuth = ({
|
||||
req.user = authPayload;
|
||||
break;
|
||||
default:
|
||||
authPayload = await getAuthUserPayload({
|
||||
const { user, tokenVersionId } = await getAuthUserPayload({
|
||||
authTokenValue
|
||||
});
|
||||
req.user = authPayload;
|
||||
authPayload = user;
|
||||
req.user = user;
|
||||
req.tokenVersionId = tokenVersionId;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -88,8 +90,9 @@ const requireAuth = ({
|
||||
authMode,
|
||||
authPayload, // User, ServiceAccount, ServiceTokenData
|
||||
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
authIP: req.ip,
|
||||
authUserAgent: req.headers['user-agent'] ?? 'other'
|
||||
authIP: req.realIP,
|
||||
authUserAgent: req.headers['user-agent'] ?? 'other',
|
||||
tokenVersionId: req.tokenVersionId
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -22,6 +22,7 @@ import Workspace, { IWorkspace } from './workspace';
|
||||
import ServiceTokenData, { IServiceTokenData } from './serviceTokenData';
|
||||
import APIKeyData, { IAPIKeyData } from './apiKeyData';
|
||||
import LoginSRPDetail, { ILoginSRPDetail } from './loginSRPDetail';
|
||||
import TokenVersion, { ITokenVersion } from './tokenVersion';
|
||||
|
||||
export {
|
||||
AuthProvider,
|
||||
@@ -72,5 +73,7 @@ export {
|
||||
APIKeyData,
|
||||
IAPIKeyData,
|
||||
LoginSRPDetail,
|
||||
ILoginSRPDetail
|
||||
ILoginSRPDetail,
|
||||
TokenVersion,
|
||||
ITokenVersion
|
||||
};
|
||||
|
||||
47
backend/src/models/tokenVersion.ts
Normal file
47
backend/src/models/tokenVersion.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Schema, model, Types, Document } from 'mongoose';
|
||||
|
||||
export interface ITokenVersion extends Document {
|
||||
user: Types.ObjectId;
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
refreshVersion: number;
|
||||
accessVersion: number;
|
||||
lastUsed: Date;
|
||||
}
|
||||
|
||||
const tokenVersionSchema = new Schema<ITokenVersion>(
|
||||
{
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
ip: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
userAgent: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
refreshVersion: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
accessVersion: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
lastUsed: {
|
||||
type: Date,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
const TokenVersion = model<ITokenVersion>('TokenVersion', tokenVersionSchema);
|
||||
|
||||
export default TokenVersion;
|
||||
@@ -21,7 +21,6 @@ export interface IUser extends Document {
|
||||
tag?: string;
|
||||
salt?: string;
|
||||
verifier?: string;
|
||||
refreshVersion?: number;
|
||||
isMfaEnabled: boolean;
|
||||
mfaMethods: boolean;
|
||||
devices: {
|
||||
@@ -91,11 +90,6 @@ const userSchema = new Schema<IUser>(
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
refreshVersion: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
select: false
|
||||
},
|
||||
isMfaEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -108,7 +102,8 @@ const userSchema = new Schema<IUser>(
|
||||
ip: String,
|
||||
userAgent: String
|
||||
}],
|
||||
default: []
|
||||
default: [],
|
||||
select: false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -44,8 +44,6 @@ router.post(
|
||||
authController.checkAuth
|
||||
);
|
||||
|
||||
|
||||
|
||||
router.get(
|
||||
'/redirect/google',
|
||||
authLimiter,
|
||||
@@ -53,12 +51,27 @@ router.get(
|
||||
scope: ['profile', 'email'],
|
||||
session: false,
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/callback/google',
|
||||
passport.authenticate('google', { failureRedirect: '/login/provider/error', session: false }),
|
||||
authController.handleAuthProviderCallback,
|
||||
)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/common-passwords',
|
||||
authLimiter,
|
||||
authController.getCommonPasswords
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/sessions',
|
||||
authLimiter,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
authController.revokeAllSessions
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
getSecretsHelper,
|
||||
getSecretsBotHelper,
|
||||
encryptSymmetricHelper,
|
||||
decryptSymmetricHelper
|
||||
} from '../helpers/bot';
|
||||
@@ -25,7 +25,7 @@ class BotService {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
}) {
|
||||
return await getSecretsHelper({
|
||||
return await getSecretsBotHelper({
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
|
||||
3
backend/src/types/express/index.d.ts
vendored
3
backend/src/types/express/index.d.ts
vendored
@@ -1,4 +1,5 @@
|
||||
import * as express from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
@@ -39,7 +40,9 @@ declare global {
|
||||
serviceTokenData: any;
|
||||
apiKeyData: any;
|
||||
query?: any;
|
||||
tokenVersionId?: Types.ObjectId;
|
||||
authData: AuthData;
|
||||
realIP: string;
|
||||
requestData: {
|
||||
[key: string]: string
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,18 +2,19 @@ import crypto from 'crypto';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { faCheck, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import jsrp from 'jsrp';
|
||||
import nacl from 'tweetnacl';
|
||||
import { encodeBase64 } from 'tweetnacl-util';
|
||||
|
||||
import { useGetCommonPasswords } from '@app/hooks/api';
|
||||
import completeAccountInformationSignup from '@app/pages/api/auth/CompleteAccountInformationSignup';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import ProjectService from '@app/services/ProjectService';
|
||||
|
||||
import InputField from '../basic/InputField';
|
||||
import passwordCheck from '../utilities/checks/PasswordCheck';
|
||||
import checkPassword from '../utilities/checks/checkPassword';
|
||||
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
|
||||
import { deriveArgonKey } from '../utilities/cryptography/crypto';
|
||||
import { saveTokenToLocalStorage } from '../utilities/saveTokenToLocalStorage';
|
||||
@@ -37,6 +38,15 @@ interface UserInfoStepProps {
|
||||
providerAuthToken?: string;
|
||||
}
|
||||
|
||||
type Errors = {
|
||||
length?: string,
|
||||
upperCase?: string,
|
||||
lowerCase?: string,
|
||||
number?: string,
|
||||
specialChar?: string,
|
||||
repeatedChar?: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the step of the sign up flow where people provife their name/surname and password
|
||||
* @param {object} obj
|
||||
@@ -63,11 +73,11 @@ export default function UserInfoStep({
|
||||
setAttributionSource,
|
||||
providerAuthToken,
|
||||
}: UserInfoStepProps): JSX.Element {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
|
||||
const [errors, setErrors] = useState<Errors>({});
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
@@ -89,12 +99,11 @@ export default function UserInfoStep({
|
||||
} else {
|
||||
setOrganizationNameError(false);
|
||||
}
|
||||
errorCheck = passwordCheck({
|
||||
|
||||
errorCheck = checkPassword({
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
|
||||
if (!errorCheck) {
|
||||
@@ -248,59 +257,45 @@ export default function UserInfoStep({
|
||||
label={t('section.password.password')}
|
||||
onChangeHandler={(pass: string) => {
|
||||
setPassword(pass);
|
||||
passwordCheck({
|
||||
checkPassword({
|
||||
password: pass,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck: false
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
|
||||
error={Object.keys(errors).length > 0}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
/>
|
||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
||||
{Object.keys(errors).length > 0 && (
|
||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-1 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorLength ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
{t('section.password.validate-length')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'} text-sm`}
|
||||
>
|
||||
{t('section.password.validate-case')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
{t('section.password.validate-number')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||
{Object.keys(errors).map((key) => {
|
||||
if (errors[key as keyof Errors]) {
|
||||
return (
|
||||
<div
|
||||
className="ml-1 flex flex-row items-top justify-start"
|
||||
key={key}
|
||||
>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-md text-red ml-0.5 mr-2.5"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">
|
||||
{errors[key as keyof Errors]}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
|
||||
@@ -17,6 +17,7 @@ const passwordCheck = ({
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
}: PasswordCheckProps) => {
|
||||
|
||||
if (!password || password.length < 14) {
|
||||
setPasswordErrorLength(true);
|
||||
errorCheck = true;
|
||||
|
||||
72
frontend/src/components/utilities/checks/checkPassword.ts
Normal file
72
frontend/src/components/utilities/checks/checkPassword.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
type Errors = {
|
||||
length?: string,
|
||||
upperCase?: string,
|
||||
lowerCase?: string,
|
||||
number?: string,
|
||||
specialChar?: string,
|
||||
repeatedChar?: string,
|
||||
commonPassword?: string
|
||||
};
|
||||
|
||||
interface CheckPasswordParams {
|
||||
password: string;
|
||||
commonPasswords: string[];
|
||||
setErrors: (value: Errors) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the password [password] is at least:
|
||||
* - 8 characters long
|
||||
* - Contains 1 uppercase character (A-Z)
|
||||
* - Contains 1 lowercase character (a-z)
|
||||
* - Contains 1 number (0-9)
|
||||
* - Does not contain 3 repeat, consecutive characters
|
||||
*
|
||||
* The function returns whether or not the password [password]
|
||||
* passes the minimum requirements above. It sets errors on
|
||||
* an erorr object via [setErrors].
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.password - the password to check
|
||||
* @param {Function} obj.setErrors - set state function to set error object
|
||||
*/
|
||||
const checkPassword = ({
|
||||
password,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
}: CheckPasswordParams): boolean => {
|
||||
const errors: Errors = {};
|
||||
|
||||
if (password.length < 8) {
|
||||
errors.length = "8 characters";
|
||||
}
|
||||
|
||||
if (!/[A-Z]/.test(password)) {
|
||||
errors.upperCase = "1 uppercase character (A-Z)";
|
||||
}
|
||||
|
||||
if (!/[a-z]/.test(password)) {
|
||||
errors.lowerCase = "1 lowercase character (a-z)";
|
||||
}
|
||||
|
||||
if (!/[0-9]/.test(password)) {
|
||||
errors.number = "1 number (0-9)";
|
||||
}
|
||||
|
||||
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
||||
errors.specialChar = "1 special character (!@#$%^&*(),.?)";
|
||||
}
|
||||
|
||||
if (/([A-Za-z0-9])\1\1\1/.test(password)) {
|
||||
errors.repeatedChar = "No 3 repeat, consecutive characters";
|
||||
}
|
||||
|
||||
if (commonPasswords.includes(password)) {
|
||||
errors.commonPassword = "No common passwords";
|
||||
}
|
||||
|
||||
setErrors(errors);
|
||||
return Object.keys(errors).length > 0;
|
||||
}
|
||||
|
||||
export default checkPassword;
|
||||
@@ -125,6 +125,10 @@ const changePassword = async (
|
||||
setPasswordChanged(true);
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
|
||||
window.location.href = '/login';
|
||||
|
||||
// move to login page
|
||||
} catch (error) {
|
||||
setCurrentPasswordError(true);
|
||||
console.log(error);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export {
|
||||
useGetAuthToken,
|
||||
useSendMfaToken,
|
||||
useVerifyMfaToken} from './queries'
|
||||
useVerifyMfaToken,
|
||||
useRevokeAllSessions,
|
||||
useGetCommonPasswords
|
||||
} from './queries'
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
VerifyMfaTokenRes} from './types';
|
||||
|
||||
const authKeys = {
|
||||
getAuthToken: ['token'] as const
|
||||
getAuthToken: ['token'] as const,
|
||||
commonPasswords: ['common-passwords'] as const
|
||||
};
|
||||
|
||||
export const useSendMfaToken = () => {
|
||||
@@ -49,3 +50,20 @@ export const useGetAuthToken = () =>
|
||||
onSuccess: (data) => setAuthToken(data.token),
|
||||
retry: 0
|
||||
});
|
||||
|
||||
export const useRevokeAllSessions = () => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiRequest.delete('/api/v1/auth/sessions');
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const fetchCommonPasswords = async () => {
|
||||
const { data } = await apiRequest.get('/api/v1/auth/common-passwords');
|
||||
return data || [];
|
||||
};
|
||||
|
||||
export const useGetCommonPasswords = () =>
|
||||
useQuery({ queryKey: authKeys.commonPasswords, queryFn: fetchCommonPasswords });
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCheck, faPlus, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBan,faCheck, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import Button from '@app/components/basic/buttons/Button';
|
||||
@@ -10,21 +10,31 @@ import InputField from '@app/components/basic/InputField';
|
||||
import ListBox from '@app/components/basic/Listbox';
|
||||
import ApiKeyTable from '@app/components/basic/table/ApiKeyTable';
|
||||
import NavHeader from '@app/components/navigation/NavHeader';
|
||||
import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
||||
import checkPassword from '@app/components/utilities/checks/checkPassword';
|
||||
import changePassword from '@app/components/utilities/cryptography/changePassword';
|
||||
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
||||
import {
|
||||
useGetCommonPasswords,
|
||||
useRevokeAllSessions} from '@app/hooks/api';
|
||||
import { SecuritySection } from '@app/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection';
|
||||
|
||||
import AddApiKeyDialog from '../../../components/basic/dialog/AddApiKeyDialog';
|
||||
import getAPIKeys from '../../api/apiKey/getAPIKeys';
|
||||
import getUser from '../../api/user/getUser';
|
||||
|
||||
type Errors = {
|
||||
length?: string,
|
||||
upperCase?: string,
|
||||
lowerCase?: string,
|
||||
number?: string,
|
||||
specialChar?: string,
|
||||
repeatedChar?: string,
|
||||
};
|
||||
|
||||
export default function PersonalSettings() {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
const [personalEmail, setPersonalEmail] = useState('');
|
||||
const [personalName, setPersonalName] = useState('');
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
@@ -34,6 +44,9 @@ export default function PersonalSettings() {
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false);
|
||||
const [apiKeys, setApiKeys] = useState<any[]>([]);
|
||||
const [errors, setErrors] = useState<Errors>({});
|
||||
|
||||
const revokeAllSessions = useRevokeAllSessions();
|
||||
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
@@ -154,78 +167,54 @@ export default function PersonalSettings() {
|
||||
label={t('section.password.new') as string}
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck({
|
||||
checkPassword({
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck: false
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber}
|
||||
error={Object.keys(errors).length > 0}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
/>
|
||||
</div>
|
||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
||||
<div className="mt-3 mb-2 flex w-full max-w-xl flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-1 text-sm text-gray-400">
|
||||
{t('section.password.validate-base')}
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t('section.password.validate-length')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t('section.password.validate-case')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'
|
||||
} text-sm`}
|
||||
>
|
||||
{t('section.password.validate-number')}
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(errors).length > 0 && (
|
||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-2 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||
{Object.keys(errors).map((key) => {
|
||||
if (errors[key as keyof Errors]) {
|
||||
return (
|
||||
<div className="ml-1 flex flex-row items-top justify-start" key={key}>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-md text-red ml-0.5 mr-2.5"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">
|
||||
{errors[key as keyof Errors]}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
<div className="mt-3 flex w-52 flex-row items-center pr-3">
|
||||
<Button
|
||||
text={t('section.password.change') as string}
|
||||
onButtonPressed={() => {
|
||||
if (!passwordErrorLength && !passwordErrorLowerCase && !passwordErrorNumber) {
|
||||
const errorCheck = checkPassword({
|
||||
password: newPassword,
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
if (!errorCheck) {
|
||||
changePassword(
|
||||
personalEmail,
|
||||
currentPassword,
|
||||
@@ -239,11 +228,6 @@ export default function PersonalSettings() {
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={
|
||||
newPassword !== '' &&
|
||||
currentPassword !== '' &&
|
||||
!(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber)
|
||||
}
|
||||
textDisabled={t('section.password.change') as string}
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
@@ -254,6 +238,28 @@ export default function PersonalSettings() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||
<div className="my-4 flex w-full flex-row justify-between">
|
||||
<p className="text-xl font-semibold w-full">
|
||||
Sessions
|
||||
</p>
|
||||
<div className="w-40">
|
||||
<Button
|
||||
text="Revoke all"
|
||||
onButtonPressed={async () => {
|
||||
await revokeAllSessions.mutateAsync();
|
||||
router.push('/login');
|
||||
}}
|
||||
color="mineshaft"
|
||||
icon={faBan}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-5 text-sm text-mineshaft-300">
|
||||
Logging into Infisical via browser or CLI creates a session. Revoking all sessions logs your account out all active sessions across all browsers and CLIs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 mb-6 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pt-5 pb-6">
|
||||
<div className="flex w-full max-w-5xl flex-row items-center justify-between">
|
||||
|
||||
@@ -7,7 +7,7 @@ import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { faCheck, faWarning, faX } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCheck, faWarning, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import jsrp from 'jsrp';
|
||||
import queryString from 'query-string';
|
||||
@@ -17,7 +17,9 @@ import { encodeBase64 } from 'tweetnacl-util';
|
||||
import Button from '@app/components/basic/buttons/Button';
|
||||
import InputField from '@app/components/basic/InputField';
|
||||
import attemptLogin from '@app/components/utilities/attemptLogin';
|
||||
import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
||||
|
||||
import checkPassword from '@app/components/utilities/checks/checkPassword';
|
||||
|
||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||
import { deriveArgonKey } from '@app/components/utilities/cryptography/crypto';
|
||||
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
||||
@@ -25,28 +27,36 @@ import { saveTokenToLocalStorage } from '@app/components/utilities/saveTokenToLo
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
|
||||
|
||||
import {
|
||||
useGetCommonPasswords
|
||||
} from '@app/hooks/api';
|
||||
import completeAccountInformationSignupInvite from './api/auth/CompleteAccountInformationSignupInvite';
|
||||
import verifySignupInvite from './api/auth/VerifySignupInvite';
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
type Errors = {
|
||||
length?: string,
|
||||
upperCase?: string,
|
||||
lowerCase?: string,
|
||||
number?: string,
|
||||
specialChar?: string,
|
||||
repeatedChar?: string,
|
||||
};
|
||||
|
||||
export default function SignupInvite() {
|
||||
const { data: commonPasswords } = useGetCommonPasswords();
|
||||
const [password, setPassword] = useState('');
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [firstNameError, setFirstNameError] = useState(false);
|
||||
const [lastNameError, setLastNameError] = useState(false);
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [verificationToken, setVerificationToken] = useState('');
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [errors, setErrors] = useState<Errors>({});
|
||||
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||
@@ -70,12 +80,11 @@ export default function SignupInvite() {
|
||||
} else {
|
||||
setLastNameError(false);
|
||||
}
|
||||
errorCheck = passwordCheck({
|
||||
|
||||
errorCheck = checkPassword({
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
|
||||
if (!errorCheck) {
|
||||
@@ -252,60 +261,43 @@ export default function SignupInvite() {
|
||||
label="Password"
|
||||
onChangeHandler={(pass) => {
|
||||
setPassword(pass);
|
||||
passwordCheck({
|
||||
checkPassword({
|
||||
password: pass,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck: false
|
||||
commonPasswords,
|
||||
setErrors
|
||||
});
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
|
||||
error={Object.keys(errors).length > 0}
|
||||
autoComplete="new-password"
|
||||
id="new-password"
|
||||
/>
|
||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
||||
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
|
||||
<div className="text-gray-400 text-sm mb-1">Password should contain at least:</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2" />
|
||||
)}
|
||||
<div className={`${passwordErrorLength ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
14 characters
|
||||
</div>
|
||||
{Object.keys(errors).length > 0 && (
|
||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-2 text-sm text-gray-400">Password should contain at least:</div>
|
||||
{Object.keys(errors).map((key) => {
|
||||
if (errors[key as keyof Errors]) {
|
||||
return (
|
||||
<div className="ml-1 flex flex-row items-top justify-start" key={key}>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-md text-red ml-0.5 mr-2.5"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm">
|
||||
{errors[key as keyof Errors]}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'} text-sm`}
|
||||
>
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2" />
|
||||
)}
|
||||
<div className={`${passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
||||
1 number
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user