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