mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 12:26:05 +00:00
feat: remove try-catch blocks for handling errors in middleware
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
@@ -34,47 +33,39 @@ declare module 'jsonwebtoken' {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const login1 = async (req: Request, res: Response) => {
|
export const login1 = async (req: Request, res: Response) => {
|
||||||
try {
|
const {
|
||||||
const {
|
email,
|
||||||
email,
|
clientPublicKey
|
||||||
clientPublicKey
|
}: { email: string; clientPublicKey: string } = req.body;
|
||||||
}: { email: string; clientPublicKey: string } = req.body;
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
email
|
email
|
||||||
}).select('+salt +verifier');
|
}).select('+salt +verifier');
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
if (!user) throw new Error('Failed to find user');
|
||||||
|
|
||||||
const server = new jsrp.server();
|
const server = new jsrp.server();
|
||||||
server.init(
|
server.init(
|
||||||
{
|
{
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
verifier: user.verifier
|
verifier: user.verifier
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
// generate server-side public key
|
// generate server-side public key
|
||||||
const serverPublicKey = server.getPublicKey();
|
const serverPublicKey = server.getPublicKey();
|
||||||
|
|
||||||
await LoginSRPDetail.findOneAndReplace({ email: email }, {
|
await LoginSRPDetail.findOneAndReplace({ email: email }, {
|
||||||
email: email,
|
email: email,
|
||||||
clientPublicKey: clientPublicKey,
|
clientPublicKey: clientPublicKey,
|
||||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||||
}, { upsert: true, returnNewDocument: false })
|
}, { upsert: true, returnNewDocument: false })
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serverPublicKey,
|
serverPublicKey,
|
||||||
salt: user.salt
|
salt: user.salt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to start authentication process'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,84 +76,76 @@ export const login1 = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const login2 = async (req: Request, res: Response) => {
|
export const login2 = async (req: Request, res: Response) => {
|
||||||
try {
|
const { email, clientProof } = req.body;
|
||||||
const { email, clientProof } = req.body;
|
const user = await User.findOne({
|
||||||
const user = await User.findOne({
|
email
|
||||||
email
|
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
|
||||||
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
|
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
if (!user) throw new Error('Failed to find user');
|
||||||
|
|
||||||
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
|
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
|
||||||
|
|
||||||
if (!loginSRPDetailFromDB) {
|
if (!loginSRPDetailFromDB) {
|
||||||
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = new jsrp.server();
|
const server = new jsrp.server();
|
||||||
server.init(
|
server.init(
|
||||||
{
|
{
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
verifier: user.verifier,
|
verifier: user.verifier,
|
||||||
b: loginSRPDetailFromDB.serverBInt
|
b: loginSRPDetailFromDB.serverBInt
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
|
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
|
||||||
|
|
||||||
// compare server and client shared keys
|
// compare server and client shared keys
|
||||||
if (server.checkClientProof(clientProof)) {
|
if (server.checkClientProof(clientProof)) {
|
||||||
// issue tokens
|
// issue tokens
|
||||||
|
|
||||||
await checkUserDevice({
|
await checkUserDevice({
|
||||||
user,
|
user,
|
||||||
ip: req.ip,
|
ip: req.ip,
|
||||||
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.toString() });
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: await getHttpsEnabled()
|
secure: await getHttpsEnabled()
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginAction = await EELogService.createAction({
|
const loginAction = await EELogService.createAction({
|
||||||
name: ACTION_LOGIN,
|
name: ACTION_LOGIN,
|
||||||
userId: user._id
|
userId: user._id
|
||||||
});
|
});
|
||||||
|
|
||||||
loginAction && await EELogService.createLog({
|
loginAction && await EELogService.createLog({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
actions: [loginAction],
|
actions: [loginAction],
|
||||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
// return (access) token in response
|
// return (access) token in response
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
token: tokens.token,
|
token: tokens.token,
|
||||||
publicKey: user.publicKey,
|
publicKey: user.publicKey,
|
||||||
encryptedPrivateKey: user.encryptedPrivateKey,
|
encryptedPrivateKey: user.encryptedPrivateKey,
|
||||||
iv: user.iv,
|
iv: user.iv,
|
||||||
tag: user.tag
|
tag: user.tag
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to authenticate. Try again?'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
} catch (err) {
|
return res.status(400).send({
|
||||||
Sentry.setUser(null);
|
message: 'Failed to authenticate. Try again?'
|
||||||
Sentry.captureException(err);
|
});
|
||||||
return res.status(400).send({
|
}
|
||||||
message: 'Failed to authenticate. Try again?'
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,38 +155,29 @@ 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) => {
|
||||||
try {
|
await clearTokens({
|
||||||
await clearTokens({
|
userId: req.user._id.toString()
|
||||||
userId: req.user._id.toString()
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// clear httpOnly cookie
|
// clear httpOnly cookie
|
||||||
res.cookie('jid', '', {
|
res.cookie('jid', '', {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: (await getHttpsEnabled()) as boolean
|
secure: (await getHttpsEnabled()) as boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
const logoutAction = await EELogService.createAction({
|
const logoutAction = await EELogService.createAction({
|
||||||
name: ACTION_LOGOUT,
|
name: ACTION_LOGOUT,
|
||||||
userId: req.user._id
|
userId: req.user._id
|
||||||
});
|
});
|
||||||
|
|
||||||
logoutAction && await EELogService.createLog({
|
logoutAction && await EELogService.createLog({
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
actions: [logoutAction],
|
actions: [logoutAction],
|
||||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to logout'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully logged out.'
|
message: 'Successfully logged out.'
|
||||||
@@ -229,43 +203,35 @@ export const checkAuth = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getNewToken = async (req: Request, res: Response) => {
|
export const getNewToken = async (req: Request, res: Response) => {
|
||||||
try {
|
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 token in request cookies');
|
||||||
}
|
|
||||||
|
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
|
||||||
jwt.verify(refreshToken, await getJwtRefreshSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
|
||||||
_id: decodedToken.userId
|
|
||||||
}).select('+publicKey');
|
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to authenticate unfound user');
|
|
||||||
if (!user?.publicKey)
|
|
||||||
throw new Error('Failed to authenticate not fully set up account');
|
|
||||||
|
|
||||||
const token = createToken({
|
|
||||||
payload: {
|
|
||||||
userId: decodedToken.userId
|
|
||||||
},
|
|
||||||
expiresIn: await getJwtAuthLifetime(),
|
|
||||||
secret: await getJwtAuthSecret()
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
token
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Invalid request'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
|
jwt.verify(refreshToken, await getJwtRefreshSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: decodedToken.userId
|
||||||
|
}).select('+publicKey');
|
||||||
|
|
||||||
|
if (!user) throw new Error('Failed to authenticate unfound user');
|
||||||
|
if (!user?.publicKey)
|
||||||
|
throw new Error('Failed to authenticate not fully set up account');
|
||||||
|
|
||||||
|
const token = createToken({
|
||||||
|
payload: {
|
||||||
|
userId: decodedToken.userId
|
||||||
|
},
|
||||||
|
expiresIn: await getJwtAuthLifetime(),
|
||||||
|
secret: await getJwtAuthSecret()
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
token
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Bot, BotKey } from '../../models';
|
import { Bot, BotKey } from '../../models';
|
||||||
import { createBot } from '../../helpers/bot';
|
import { createBot } from '../../helpers/bot';
|
||||||
|
|
||||||
@@ -17,33 +16,24 @@ interface BotKey {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getBotByWorkspaceId = async (req: Request, res: Response) => {
|
export const getBotByWorkspaceId = async (req: Request, res: Response) => {
|
||||||
let bot;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
bot = await Bot.findOne({
|
let bot = await Bot.findOne({
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!bot) {
|
if (!bot) {
|
||||||
// case: bot doesn't exist for workspace with id [workspaceId]
|
// case: bot doesn't exist for workspace with id [workspaceId]
|
||||||
// -> create a new bot and return it
|
// -> create a new bot and return it
|
||||||
bot = await createBot({
|
bot = await createBot({
|
||||||
name: 'Infisical Bot',
|
name: 'Infisical Bot',
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get bot for workspace'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
bot
|
bot
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,54 +43,44 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const setBotActiveState = async (req: Request, res: Response) => {
|
export const setBotActiveState = async (req: Request, res: Response) => {
|
||||||
let bot;
|
const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body;
|
||||||
try {
|
|
||||||
const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body;
|
if (isActive) {
|
||||||
|
// bot state set to active -> share workspace key with bot
|
||||||
if (isActive) {
|
if (!botKey?.encryptedKey || !botKey?.nonce) {
|
||||||
// bot state set to active -> share workspace key with bot
|
return res.status(400).send({
|
||||||
if (!botKey?.encryptedKey || !botKey?.nonce) {
|
message: 'Failed to set bot state to active - missing bot key'
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to set bot state to active - missing bot key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await BotKey.findOneAndUpdate({
|
|
||||||
workspace: req.bot.workspace
|
|
||||||
}, {
|
|
||||||
encryptedKey: botKey.encryptedKey,
|
|
||||||
nonce: botKey.nonce,
|
|
||||||
sender: req.user._id,
|
|
||||||
bot: req.bot._id,
|
|
||||||
workspace: req.bot.workspace
|
|
||||||
}, {
|
|
||||||
upsert: true,
|
|
||||||
new: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// case: bot state set to inactive -> delete bot's workspace key
|
|
||||||
await BotKey.deleteOne({
|
|
||||||
bot: req.bot._id
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bot = await Bot.findOneAndUpdate({
|
await BotKey.findOneAndUpdate({
|
||||||
_id: req.bot._id
|
workspace: req.bot.workspace
|
||||||
}, {
|
}, {
|
||||||
isActive
|
encryptedKey: botKey.encryptedKey,
|
||||||
|
nonce: botKey.nonce,
|
||||||
|
sender: req.user._id,
|
||||||
|
bot: req.bot._id,
|
||||||
|
workspace: req.bot.workspace
|
||||||
}, {
|
}, {
|
||||||
|
upsert: true,
|
||||||
new: true
|
new: true
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
if (!bot) throw new Error('Failed to update bot active state');
|
// case: bot state set to inactive -> delete bot's workspace key
|
||||||
|
await BotKey.deleteOne({
|
||||||
} catch (err) {
|
bot: req.bot._id
|
||||||
Sentry.setUser({ email: req.user.email });
|
});
|
||||||
Sentry.captureException(err);
|
}
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to update bot active state'
|
let bot = await Bot.findOneAndUpdate({
|
||||||
});
|
_id: req.bot._id
|
||||||
}
|
}, {
|
||||||
|
isActive
|
||||||
|
}, {
|
||||||
|
new: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!bot) throw new Error('Failed to update bot active state');
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
bot
|
bot
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
import {
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
Bot
|
Bot
|
||||||
@@ -22,22 +21,13 @@ import { standardRequest } from '../../config/request';
|
|||||||
* Return integration authorization with id [integrationAuthId]
|
* Return integration authorization with id [integrationAuthId]
|
||||||
*/
|
*/
|
||||||
export const getIntegrationAuth = async (req: Request, res: Response) => {
|
export const getIntegrationAuth = async (req: Request, res: Response) => {
|
||||||
let integrationAuth;
|
const { integrationAuthId } = req.params;
|
||||||
try {
|
const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||||
const { integrationAuthId } = req.params;
|
|
||||||
integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
if (!integrationAuth) return res.status(400).send({
|
||||||
|
message: 'Failed to find integration authorization'
|
||||||
if (!integrationAuth) return res.status(400).send({
|
});
|
||||||
message: 'Failed to find integration authorization'
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get integration authorization'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrationAuth
|
integrationAuth
|
||||||
});
|
});
|
||||||
@@ -61,33 +51,25 @@ export const oAuthExchange = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
try {
|
const { workspaceId, code, integration } = req.body;
|
||||||
const { workspaceId, code, integration } = req.body;
|
if (!INTEGRATION_SET.has(integration))
|
||||||
if (!INTEGRATION_SET.has(integration))
|
throw new Error('Failed to validate integration');
|
||||||
throw new Error('Failed to validate integration');
|
|
||||||
|
const environments = req.membership.workspace?.environments || [];
|
||||||
const environments = req.membership.workspace?.environments || [];
|
if(environments.length === 0){
|
||||||
if(environments.length === 0){
|
throw new Error("Failed to get environments")
|
||||||
throw new Error("Failed to get environments")
|
}
|
||||||
}
|
|
||||||
|
const integrationAuth = await IntegrationService.handleOAuthExchange({
|
||||||
const integrationAuth = await IntegrationService.handleOAuthExchange({
|
workspaceId,
|
||||||
workspaceId,
|
integration,
|
||||||
integration,
|
code,
|
||||||
code,
|
environment: environments[0].slug,
|
||||||
environment: environments[0].slug,
|
});
|
||||||
});
|
|
||||||
|
return res.status(200).send({
|
||||||
return res.status(200).send({
|
integrationAuth
|
||||||
integrationAuth
|
});
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get OAuth2 code-token exchange'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,55 +86,47 @@ export const saveIntegrationAccessToken = async (
|
|||||||
// TODO: check if access token is valid for each integration
|
// TODO: check if access token is valid for each integration
|
||||||
|
|
||||||
let integrationAuth;
|
let integrationAuth;
|
||||||
try {
|
const {
|
||||||
const {
|
workspaceId,
|
||||||
workspaceId,
|
accessId,
|
||||||
accessId,
|
accessToken,
|
||||||
accessToken,
|
integration
|
||||||
integration
|
}: {
|
||||||
}: {
|
workspaceId: string;
|
||||||
workspaceId: string;
|
accessId: string | null;
|
||||||
accessId: string | null;
|
accessToken: string;
|
||||||
accessToken: string;
|
integration: string;
|
||||||
integration: string;
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
isActive: true
|
isActive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!bot) throw new Error('Bot must be enabled to save integration access token');
|
if (!bot) throw new Error('Bot must be enabled to save integration access token');
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
integration
|
integration
|
||||||
}, {
|
}, {
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
integration,
|
integration,
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
}, {
|
}, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// encrypt and save integration access details
|
// encrypt and save integration access details
|
||||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessExpiresAt: undefined
|
accessExpiresAt: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error('Failed to save integration access token');
|
if (!integrationAuth) throw new Error('Failed to save integration access token');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to save access token for integration'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrationAuth
|
integrationAuth
|
||||||
@@ -166,22 +140,13 @@ export const saveIntegrationAccessToken = async (
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||||
let apps;
|
const teamId = req.query.teamId as string;
|
||||||
try {
|
|
||||||
const teamId = req.query.teamId as string;
|
const apps = await getApps({
|
||||||
|
integrationAuth: req.integrationAuth,
|
||||||
apps = await getApps({
|
accessToken: req.accessToken,
|
||||||
integrationAuth: req.integrationAuth,
|
...teamId && { teamId }
|
||||||
accessToken: req.accessToken,
|
});
|
||||||
...teamId && { teamId }
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get integration authorization applications",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
apps
|
apps
|
||||||
@@ -402,19 +367,10 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||||
let integrationAuth;
|
const integrationAuth = await revokeAccess({
|
||||||
try {
|
integrationAuth: req.integrationAuth,
|
||||||
integrationAuth = await revokeAccess({
|
accessToken: req.accessToken,
|
||||||
integrationAuth: req.integrationAuth,
|
});
|
||||||
accessToken: req.accessToken,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to delete integration authorization",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
import {
|
||||||
Integration
|
Integration
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
@@ -14,62 +13,50 @@ import { eventPushSecrets } from '../../events';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createIntegration = async (req: Request, res: Response) => {
|
export const createIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
const {
|
||||||
|
integrationAuthId,
|
||||||
try {
|
app,
|
||||||
const {
|
appId,
|
||||||
integrationAuthId,
|
isActive,
|
||||||
app,
|
sourceEnvironment,
|
||||||
appId,
|
targetEnvironment,
|
||||||
isActive,
|
targetEnvironmentId,
|
||||||
sourceEnvironment,
|
targetService,
|
||||||
targetEnvironment,
|
targetServiceId,
|
||||||
targetEnvironmentId,
|
owner,
|
||||||
targetService,
|
path,
|
||||||
targetServiceId,
|
region
|
||||||
owner,
|
} = req.body;
|
||||||
path,
|
|
||||||
region
|
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
|
||||||
|
|
||||||
// initialize new integration after saving integration access token
|
|
||||||
integration = await new Integration({
|
|
||||||
workspace: req.integrationAuth.workspace._id,
|
|
||||||
environment: sourceEnvironment,
|
|
||||||
isActive,
|
|
||||||
app,
|
|
||||||
appId,
|
|
||||||
targetEnvironment,
|
|
||||||
targetEnvironmentId,
|
|
||||||
targetService,
|
|
||||||
targetServiceId,
|
|
||||||
owner,
|
|
||||||
path,
|
|
||||||
region,
|
|
||||||
integration: req.integrationAuth.integration,
|
|
||||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
if (integration) {
|
|
||||||
// trigger event - push secrets
|
|
||||||
EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
environment: sourceEnvironment
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to create integration'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// initialize new integration after saving integration access token
|
||||||
|
const integration = await new Integration({
|
||||||
|
workspace: req.integrationAuth.workspace._id,
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
isActive,
|
||||||
|
app,
|
||||||
|
appId,
|
||||||
|
targetEnvironment,
|
||||||
|
targetEnvironmentId,
|
||||||
|
targetService,
|
||||||
|
targetServiceId,
|
||||||
|
owner,
|
||||||
|
path,
|
||||||
|
region,
|
||||||
|
integration: req.integrationAuth.integration,
|
||||||
|
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
if (integration) {
|
||||||
|
// trigger event - push secrets
|
||||||
|
EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
environment: sourceEnvironment
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration,
|
integration,
|
||||||
});
|
});
|
||||||
@@ -82,52 +69,43 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateIntegration = async (req: Request, res: Response) => {
|
export const updateIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
|
||||||
|
|
||||||
// TODO: add integration-specific validation to ensure that each
|
// TODO: add integration-specific validation to ensure that each
|
||||||
// integration has the correct fields populated in [Integration]
|
// integration has the correct fields populated in [Integration]
|
||||||
|
|
||||||
try {
|
const {
|
||||||
const {
|
environment,
|
||||||
|
isActive,
|
||||||
|
app,
|
||||||
|
appId,
|
||||||
|
targetEnvironment,
|
||||||
|
owner, // github-specific integration param
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const integration = await Integration.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: req.integration._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
environment,
|
environment,
|
||||||
isActive,
|
isActive,
|
||||||
app,
|
app,
|
||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner, // github-specific integration param
|
owner,
|
||||||
} = req.body;
|
},
|
||||||
|
{
|
||||||
integration = await Integration.findOneAndUpdate(
|
new: true,
|
||||||
{
|
|
||||||
_id: req.integration._id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
environment,
|
|
||||||
isActive,
|
|
||||||
app,
|
|
||||||
appId,
|
|
||||||
targetEnvironment,
|
|
||||||
owner,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (integration) {
|
|
||||||
// trigger event - push secrets
|
|
||||||
EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: integration.workspace,
|
|
||||||
environment
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
);
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
if (integration) {
|
||||||
return res.status(400).send({
|
// trigger event - push secrets
|
||||||
message: "Failed to update integration",
|
EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
environment
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,22 +122,13 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteIntegration = async (req: Request, res: Response) => {
|
export const deleteIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
const { integrationId } = req.params;
|
||||||
try {
|
|
||||||
const { integrationId } = req.params;
|
|
||||||
|
|
||||||
integration = await Integration.findOneAndDelete({
|
const integration = await Integration.findOneAndDelete({
|
||||||
_id: integrationId,
|
_id: integrationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integration) throw new Error("Failed to find integration");
|
if (!integration) throw new Error("Failed to find integration");
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to delete integration",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration,
|
integration,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Key } from '../../models';
|
import { Key } from '../../models';
|
||||||
import { findMembership } from '../../helpers/membership';
|
import { findMembership } from '../../helpers/membership';
|
||||||
|
|
||||||
@@ -11,34 +10,26 @@ import { findMembership } from '../../helpers/membership';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const uploadKey = async (req: Request, res: Response) => {
|
export const uploadKey = async (req: Request, res: Response) => {
|
||||||
try {
|
const { workspaceId } = req.params;
|
||||||
const { workspaceId } = req.params;
|
const { key } = req.body;
|
||||||
const { key } = req.body;
|
|
||||||
|
|
||||||
// validate membership of receiver
|
// validate membership of receiver
|
||||||
const receiverMembership = await findMembership({
|
const receiverMembership = await findMembership({
|
||||||
user: key.userId,
|
user: key.userId,
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!receiverMembership) {
|
if (!receiverMembership) {
|
||||||
throw new Error('Failed receiver membership validation for workspace');
|
throw new Error('Failed receiver membership validation for workspace');
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Key({
|
await new Key({
|
||||||
encryptedKey: key.encryptedKey,
|
encryptedKey: key.encryptedKey,
|
||||||
nonce: key.nonce,
|
nonce: key.nonce,
|
||||||
sender: req.user._id,
|
sender: req.user._id,
|
||||||
receiver: key.userId,
|
receiver: key.userId,
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
}).save();
|
}).save();
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to upload key to workspace'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully uploaded key to workspace'
|
message: 'Successfully uploaded key to workspace'
|
||||||
@@ -52,25 +43,16 @@ export const uploadKey = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getLatestKey = async (req: Request, res: Response) => {
|
export const getLatestKey = async (req: Request, res: Response) => {
|
||||||
let latestKey;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
// get latest key
|
// get latest key
|
||||||
latestKey = await Key.find({
|
const latestKey = await Key.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
receiver: req.user._id
|
receiver: req.user._id
|
||||||
})
|
})
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.populate('sender', '+publicKey');
|
.populate('sender', '+publicKey');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get latest key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const resObj: any = {};
|
const resObj: any = {};
|
||||||
|
|
||||||
@@ -79,4 +61,4 @@ export const getLatestKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send(resObj);
|
return res.status(200).send(resObj);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { Membership, MembershipOrg, User, Key } from '../../models';
|
import { Membership, MembershipOrg, User, Key } from '../../models';
|
||||||
import {
|
import {
|
||||||
@@ -16,25 +15,16 @@ import { getSiteURL } from '../../config';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const validateMembership = async (req: Request, res: Response) => {
|
export const validateMembership = async (req: Request, res: Response) => {
|
||||||
try {
|
const { workspaceId } = req.params;
|
||||||
const { workspaceId } = req.params;
|
// validate membership
|
||||||
|
const membership = await findMembership({
|
||||||
|
user: req.user._id,
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
// validate membership
|
if (!membership) {
|
||||||
const membership = await findMembership({
|
throw new Error('Failed to validate membership');
|
||||||
user: req.user._id,
|
}
|
||||||
workspace: workspaceId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!membership) {
|
|
||||||
throw new Error('Failed to validate membership');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed workspace connection check'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Workspace membership confirmed'
|
message: 'Workspace membership confirmed'
|
||||||
@@ -48,48 +38,39 @@ export const validateMembership = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteMembership = async (req: Request, res: Response) => {
|
export const deleteMembership = async (req: Request, res: Response) => {
|
||||||
let deletedMembership;
|
const { membershipId } = req.params;
|
||||||
try {
|
|
||||||
const { membershipId } = req.params;
|
|
||||||
|
|
||||||
// check if membership to delete exists
|
// check if membership to delete exists
|
||||||
const membershipToDelete = await Membership.findOne({
|
const membershipToDelete = await Membership.findOne({
|
||||||
_id: membershipId
|
_id: membershipId
|
||||||
}).populate('user');
|
}).populate('user');
|
||||||
|
|
||||||
if (!membershipToDelete) {
|
if (!membershipToDelete) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Failed to delete workspace membership that doesn't exist"
|
"Failed to delete workspace membership that doesn't exist"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if user is a member and admin of the workspace
|
// check if user is a member and admin of the workspace
|
||||||
// whose membership we wish to delete
|
// whose membership we wish to delete
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
workspace: membershipToDelete.workspace
|
workspace: membershipToDelete.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new Error('Failed to validate workspace membership');
|
throw new Error('Failed to validate workspace membership');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membership.role !== ADMIN) {
|
if (membership.role !== ADMIN) {
|
||||||
// user is not an admin member of the workspace
|
// user is not an admin member of the workspace
|
||||||
throw new Error('Insufficient role for deleting workspace membership');
|
throw new Error('Insufficient role for deleting workspace membership');
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete workspace membership
|
// delete workspace membership
|
||||||
deletedMembership = await deleteMember({
|
const deletedMembership = await deleteMember({
|
||||||
membershipId: membershipToDelete._id.toString()
|
membershipId: membershipToDelete._id.toString()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
deletedMembership
|
deletedMembership
|
||||||
@@ -103,49 +84,40 @@ export const deleteMembership = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const changeMembershipRole = async (req: Request, res: Response) => {
|
export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||||
let membershipToChangeRole;
|
const { membershipId } = req.params;
|
||||||
try {
|
const { role } = req.body;
|
||||||
const { membershipId } = req.params;
|
|
||||||
const { role } = req.body;
|
|
||||||
|
|
||||||
if (![ADMIN, MEMBER].includes(role)) {
|
if (![ADMIN, MEMBER].includes(role)) {
|
||||||
throw new Error('Failed to validate role');
|
throw new Error('Failed to validate role');
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate target membership
|
// validate target membership
|
||||||
membershipToChangeRole = await findMembership({
|
const membershipToChangeRole = await findMembership({
|
||||||
_id: membershipId
|
_id: membershipId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipToChangeRole) {
|
if (!membershipToChangeRole) {
|
||||||
throw new Error('Failed to find membership to change role');
|
throw new Error('Failed to find membership to change role');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if user is a member and admin of target membership's
|
// check if user is a member and admin of target membership's
|
||||||
// workspace
|
// workspace
|
||||||
const membership = await findMembership({
|
const membership = await findMembership({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
workspace: membershipToChangeRole.workspace
|
workspace: membershipToChangeRole.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new Error('Failed to validate membership');
|
throw new Error('Failed to validate membership');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membership.role !== ADMIN) {
|
if (membership.role !== ADMIN) {
|
||||||
// user is not an admin member of the workspace
|
// user is not an admin member of the workspace
|
||||||
throw new Error('Insufficient role for changing member roles');
|
throw new Error('Insufficient role for changing member roles');
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipToChangeRole.role = role;
|
membershipToChangeRole.role = role;
|
||||||
await membershipToChangeRole.save();
|
await membershipToChangeRole.save();
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to change membership role'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership: membershipToChangeRole
|
membership: membershipToChangeRole
|
||||||
@@ -159,75 +131,66 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const inviteUserToWorkspace = async (req: Request, res: Response) => {
|
export const inviteUserToWorkspace = async (req: Request, res: Response) => {
|
||||||
let invitee, latestKey;
|
const { workspaceId } = req.params;
|
||||||
try {
|
const { email }: { email: string } = req.body;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
const { email }: { email: string } = req.body;
|
|
||||||
|
|
||||||
invitee = await User.findOne({
|
const invitee = await User.findOne({
|
||||||
email
|
email
|
||||||
}).select('+publicKey');
|
}).select('+publicKey');
|
||||||
|
|
||||||
if (!invitee || !invitee?.publicKey)
|
if (!invitee || !invitee?.publicKey)
|
||||||
throw new Error('Failed to validate invitee');
|
throw new Error('Failed to validate invitee');
|
||||||
|
|
||||||
// validate invitee's workspace membership - ensure member isn't
|
// validate invitee's workspace membership - ensure member isn't
|
||||||
// already a member of the workspace
|
// already a member of the workspace
|
||||||
const inviteeMembership = await Membership.findOne({
|
const inviteeMembership = await Membership.findOne({
|
||||||
user: invitee._id,
|
user: invitee._id,
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (inviteeMembership)
|
if (inviteeMembership)
|
||||||
throw new Error('Failed to add existing member of workspace');
|
throw new Error('Failed to add existing member of workspace');
|
||||||
|
|
||||||
// validate invitee's organization membership - ensure that only
|
// validate invitee's organization membership - ensure that only
|
||||||
// (accepted) organization members can be added to the workspace
|
// (accepted) organization members can be added to the workspace
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: invitee._id,
|
user: invitee._id,
|
||||||
organization: req.membership.workspace.organization,
|
organization: req.membership.workspace.organization,
|
||||||
status: ACCEPTED
|
status: ACCEPTED
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg)
|
if (!membershipOrg)
|
||||||
throw new Error("Failed to validate invitee's organization membership");
|
throw new Error("Failed to validate invitee's organization membership");
|
||||||
|
|
||||||
// get latest key
|
// get latest key
|
||||||
latestKey = await Key.findOne({
|
const latestKey = await Key.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
receiver: req.user._id
|
receiver: req.user._id
|
||||||
})
|
})
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.populate('sender', '+publicKey');
|
.populate('sender', '+publicKey');
|
||||||
|
|
||||||
// create new workspace membership
|
// create new workspace membership
|
||||||
const m = await new Membership({
|
const m = await new Membership({
|
||||||
user: invitee._id,
|
user: invitee._id,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
role: MEMBER
|
role: MEMBER
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
await sendMail({
|
await sendMail({
|
||||||
template: 'workspaceInvitation.handlebars',
|
template: 'workspaceInvitation.handlebars',
|
||||||
subjectLine: 'Infisical workspace invitation',
|
subjectLine: 'Infisical workspace invitation',
|
||||||
recipients: [invitee.email],
|
recipients: [invitee.email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
inviterFirstName: req.user.firstName,
|
inviterFirstName: req.user.firstName,
|
||||||
inviterEmail: req.user.email,
|
inviterEmail: req.user.email,
|
||||||
workspaceName: req.membership.workspace.name,
|
workspaceName: req.membership.workspace.name,
|
||||||
callback_url: (await getSiteURL()) + '/login'
|
callback_url: (await getSiteURL()) + '/login'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to invite user to workspace'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
invitee,
|
invitee,
|
||||||
latestKey
|
latestKey
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { MembershipOrg, Organization, User } from '../../models';
|
import { MembershipOrg, Organization, User } from '../../models';
|
||||||
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg';
|
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg';
|
||||||
import { createToken } from '../../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
@@ -17,52 +16,43 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteMembershipOrg = async (req: Request, res: Response) => {
|
export const deleteMembershipOrg = async (req: Request, res: Response) => {
|
||||||
let membershipOrgToDelete;
|
const { membershipOrgId } = req.params;
|
||||||
try {
|
|
||||||
const { membershipOrgId } = req.params;
|
|
||||||
|
|
||||||
// check if organization membership to delete exists
|
// check if organization membership to delete exists
|
||||||
membershipOrgToDelete = await MembershipOrg.findOne({
|
const membershipOrgToDelete = await MembershipOrg.findOne({
|
||||||
_id: membershipOrgId
|
_id: membershipOrgId
|
||||||
}).populate('user');
|
}).populate('user');
|
||||||
|
|
||||||
if (!membershipOrgToDelete) {
|
if (!membershipOrgToDelete) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Failed to delete organization membership that doesn't exist"
|
"Failed to delete organization membership that doesn't exist"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if user is a member and admin of the organization
|
// check if user is a member and admin of the organization
|
||||||
// whose membership we wish to delete
|
// whose membership we wish to delete
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
organization: membershipOrgToDelete.organization
|
organization: membershipOrgToDelete.organization
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg) {
|
if (!membershipOrg) {
|
||||||
throw new Error('Failed to validate organization membership');
|
throw new Error('Failed to validate organization membership');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) {
|
if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) {
|
||||||
// user is not an admin member of the organization
|
// user is not an admin member of the organization
|
||||||
throw new Error('Insufficient role for deleting organization membership');
|
throw new Error('Insufficient role for deleting organization membership');
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete organization membership
|
// delete organization membership
|
||||||
const deletedMembershipOrg = await deleteMemberFromOrg({
|
const deletedMembershipOrg = await deleteMemberFromOrg({
|
||||||
membershipOrgId: membershipOrgToDelete._id.toString()
|
membershipOrgId: membershipOrgToDelete._id.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateSubscriptionOrgQuantity({
|
await updateSubscriptionOrgQuantity({
|
||||||
organizationId: membershipOrg.organization.toString()
|
organizationId: membershipOrg.organization.toString()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete organization membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return membershipOrgToDelete;
|
return membershipOrgToDelete;
|
||||||
};
|
};
|
||||||
@@ -78,14 +68,6 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
|||||||
// [membershipOrgId]
|
// [membershipOrgId]
|
||||||
|
|
||||||
let membershipToChangeRole;
|
let membershipToChangeRole;
|
||||||
// try {
|
|
||||||
// } catch (err) {
|
|
||||||
// Sentry.setUser({ email: req.user.email });
|
|
||||||
// Sentry.captureException(err);
|
|
||||||
// return res.status(400).send({
|
|
||||||
// message: 'Failed to change organization membership role'
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membershipOrg: membershipToChangeRole
|
membershipOrg: membershipToChangeRole
|
||||||
@@ -101,106 +83,98 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||||
let invitee, inviteeMembershipOrg, completeInviteLink;
|
let invitee, inviteeMembershipOrg, completeInviteLink;
|
||||||
try {
|
const { organizationId, inviteeEmail } = req.body;
|
||||||
const { organizationId, inviteeEmail } = req.body;
|
const host = req.headers.host;
|
||||||
const host = req.headers.host;
|
const siteUrl = `${req.protocol}://${host}`;
|
||||||
const siteUrl = `${req.protocol}://${host}`;
|
|
||||||
|
|
||||||
// validate membership
|
// validate membership
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg) {
|
if (!membershipOrg) {
|
||||||
throw new Error('Failed to validate organization membership');
|
throw new Error('Failed to validate organization membership');
|
||||||
}
|
}
|
||||||
|
|
||||||
invitee = await User.findOne({
|
invitee = await User.findOne({
|
||||||
email: inviteeEmail
|
email: inviteeEmail
|
||||||
}).select('+publicKey');
|
}).select('+publicKey');
|
||||||
|
|
||||||
if (invitee) {
|
if (invitee) {
|
||||||
// case: invitee is an existing user
|
// case: invitee is an existing user
|
||||||
|
|
||||||
inviteeMembershipOrg = await MembershipOrg.findOne({
|
inviteeMembershipOrg = await MembershipOrg.findOne({
|
||||||
user: invitee._id,
|
user: invitee._id,
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) {
|
if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Failed to invite an existing member of the organization'
|
'Failed to invite an existing member of the organization'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inviteeMembershipOrg) {
|
if (!inviteeMembershipOrg) {
|
||||||
|
|
||||||
await new MembershipOrg({
|
await new MembershipOrg({
|
||||||
user: invitee,
|
user: invitee,
|
||||||
inviteEmail: inviteeEmail,
|
inviteEmail: inviteeEmail,
|
||||||
organization: organizationId,
|
organization: organizationId,
|
||||||
role: MEMBER,
|
role: MEMBER,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// check if invitee has been invited before
|
// check if invitee has been invited before
|
||||||
inviteeMembershipOrg = await MembershipOrg.findOne({
|
inviteeMembershipOrg = await MembershipOrg.findOne({
|
||||||
inviteEmail: inviteeEmail,
|
inviteEmail: inviteeEmail,
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!inviteeMembershipOrg) {
|
if (!inviteeMembershipOrg) {
|
||||||
// case: invitee has never been invited before
|
// case: invitee has never been invited before
|
||||||
|
|
||||||
await new MembershipOrg({
|
await new MembershipOrg({
|
||||||
inviteEmail: inviteeEmail,
|
inviteEmail: inviteeEmail,
|
||||||
organization: organizationId,
|
organization: organizationId,
|
||||||
role: MEMBER,
|
role: MEMBER,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const organization = await Organization.findOne({ _id: organizationId });
|
const organization = await Organization.findOne({ _id: organizationId });
|
||||||
|
|
||||||
if (organization) {
|
if (organization) {
|
||||||
|
|
||||||
const token = await TokenService.createToken({
|
const token = await TokenService.createToken({
|
||||||
type: TOKEN_EMAIL_ORG_INVITATION,
|
type: TOKEN_EMAIL_ORG_INVITATION,
|
||||||
email: inviteeEmail,
|
email: inviteeEmail,
|
||||||
organizationId: organization._id
|
organizationId: organization._id
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendMail({
|
await sendMail({
|
||||||
template: 'organizationInvitation.handlebars',
|
template: 'organizationInvitation.handlebars',
|
||||||
subjectLine: 'Infisical organization invitation',
|
subjectLine: 'Infisical organization invitation',
|
||||||
recipients: [inviteeEmail],
|
recipients: [inviteeEmail],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
inviterFirstName: req.user.firstName,
|
inviterFirstName: req.user.firstName,
|
||||||
inviterEmail: req.user.email,
|
inviterEmail: req.user.email,
|
||||||
organizationName: organization.name,
|
organizationName: organization.name,
|
||||||
email: inviteeEmail,
|
email: inviteeEmail,
|
||||||
organizationId: organization._id.toString(),
|
organizationId: organization._id.toString(),
|
||||||
token,
|
token,
|
||||||
callback_url: (await getSiteURL()) + '/signupinvite'
|
callback_url: (await getSiteURL()) + '/signupinvite'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(await getSmtpConfigured())) {
|
if (!(await getSmtpConfigured())) {
|
||||||
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`
|
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateSubscriptionOrgQuantity({ organizationId });
|
await updateSubscriptionOrgQuantity({ organizationId });
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to send organization invite'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: `Sent an invite link to ${req.body.inviteeEmail}`,
|
message: `Sent an invite link to ${req.body.inviteeEmail}`,
|
||||||
@@ -216,70 +190,62 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
||||||
let user, token;
|
let user;
|
||||||
try {
|
const {
|
||||||
const {
|
email,
|
||||||
email,
|
organizationId,
|
||||||
organizationId,
|
code
|
||||||
code
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
user = await User.findOne({ email }).select('+publicKey');
|
user = await User.findOne({ email }).select('+publicKey');
|
||||||
|
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
status: INVITED,
|
status: INVITED,
|
||||||
organization: new Types.ObjectId(organizationId)
|
organization: new Types.ObjectId(organizationId)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg)
|
if (!membershipOrg)
|
||||||
throw new Error('Failed to find any invitations for email');
|
throw new Error('Failed to find any invitations for email');
|
||||||
|
|
||||||
await TokenService.validateToken({
|
await TokenService.validateToken({
|
||||||
type: TOKEN_EMAIL_ORG_INVITATION,
|
type: TOKEN_EMAIL_ORG_INVITATION,
|
||||||
email,
|
email,
|
||||||
organizationId: membershipOrg.organization,
|
organizationId: membershipOrg.organization,
|
||||||
token: code
|
token: code
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user && user?.publicKey) {
|
if (user && user?.publicKey) {
|
||||||
// case: user has already completed account
|
// case: user has already completed account
|
||||||
// membership can be approved and redirected to login/dashboard
|
// membership can be approved and redirected to login/dashboard
|
||||||
membershipOrg.status = ACCEPTED;
|
membershipOrg.status = ACCEPTED;
|
||||||
await membershipOrg.save();
|
await membershipOrg.save();
|
||||||
|
|
||||||
await updateSubscriptionOrgQuantity({
|
await updateSubscriptionOrgQuantity({
|
||||||
organizationId
|
organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully verified email',
|
message: 'Successfully verified email',
|
||||||
user,
|
user,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// initialize user account
|
// initialize user account
|
||||||
user = await new User({
|
user = await new User({
|
||||||
email
|
email
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate temporary signup token
|
// generate temporary signup token
|
||||||
token = createToken({
|
const token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
userId: user._id.toString()
|
userId: user._id.toString()
|
||||||
},
|
},
|
||||||
expiresIn: await getJwtSignupLifetime(),
|
expiresIn: await getJwtSignupLifetime(),
|
||||||
secret: await getJwtSignupSecret()
|
secret: await getJwtSignupSecret()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed email magic link verification for organization invitation'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully verified email',
|
message: 'Successfully verified email',
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import {
|
import {
|
||||||
@@ -15,21 +14,12 @@ import _ from 'lodash';
|
|||||||
import { getStripeSecretKey, getSiteURL } from '../../config';
|
import { getStripeSecretKey, getSiteURL } from '../../config';
|
||||||
|
|
||||||
export const getOrganizations = async (req: Request, res: Response) => {
|
export const getOrganizations = async (req: Request, res: Response) => {
|
||||||
let organizations;
|
const organizations = (
|
||||||
try {
|
await MembershipOrg.find({
|
||||||
organizations = (
|
user: req.user._id,
|
||||||
await MembershipOrg.find({
|
status: ACCEPTED
|
||||||
user: req.user._id,
|
}).populate('organization')
|
||||||
status: ACCEPTED
|
).map((m) => m.organization);
|
||||||
}).populate('organization')
|
|
||||||
).map((m) => m.organization);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get organizations'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organizations
|
organizations
|
||||||
@@ -44,33 +34,24 @@ export const getOrganizations = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createOrganization = async (req: Request, res: Response) => {
|
export const createOrganization = async (req: Request, res: Response) => {
|
||||||
let organization;
|
const { organizationName } = req.body;
|
||||||
try {
|
|
||||||
const { organizationName } = req.body;
|
|
||||||
|
|
||||||
if (organizationName.length < 1) {
|
if (organizationName.length < 1) {
|
||||||
throw new Error('Organization names must be at least 1-character long');
|
throw new Error('Organization names must be at least 1-character long');
|
||||||
}
|
}
|
||||||
|
|
||||||
// create organization and add user as member
|
// create organization and add user as member
|
||||||
organization = await create({
|
const organization = await create({
|
||||||
email: req.user.email,
|
email: req.user.email,
|
||||||
name: organizationName
|
name: organizationName
|
||||||
});
|
});
|
||||||
|
|
||||||
await addMembershipsOrg({
|
await addMembershipsOrg({
|
||||||
userIds: [req.user._id.toString()],
|
userIds: [req.user._id.toString()],
|
||||||
organizationId: organization._id.toString(),
|
organizationId: organization._id.toString(),
|
||||||
roles: [OWNER],
|
roles: [OWNER],
|
||||||
statuses: [ACCEPTED]
|
statuses: [ACCEPTED]
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to create organization'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization
|
organization
|
||||||
@@ -84,17 +65,7 @@ export const createOrganization = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganization = async (req: Request, res: Response) => {
|
export const getOrganization = async (req: Request, res: Response) => {
|
||||||
let organization;
|
const organization = req.organization
|
||||||
try {
|
|
||||||
organization = req.organization
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to find organization'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization
|
organization
|
||||||
});
|
});
|
||||||
@@ -107,20 +78,11 @@ export const getOrganization = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationMembers = async (req: Request, res: Response) => {
|
export const getOrganizationMembers = async (req: Request, res: Response) => {
|
||||||
let users;
|
const { organizationId } = req.params;
|
||||||
try {
|
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
users = await MembershipOrg.find({
|
const users = await MembershipOrg.find({
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
}).populate('user', '+publicKey');
|
}).populate('user', '+publicKey');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get organization members'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
users
|
users
|
||||||
@@ -137,35 +99,26 @@ export const getOrganizationWorkspaces = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let workspaces;
|
const { organizationId } = req.params;
|
||||||
try {
|
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
const workspacesSet = new Set(
|
const workspacesSet = new Set(
|
||||||
(
|
(
|
||||||
await Workspace.find(
|
await Workspace.find(
|
||||||
{
|
{
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
},
|
},
|
||||||
'_id'
|
'_id'
|
||||||
)
|
)
|
||||||
).map((w) => w._id.toString())
|
).map((w) => w._id.toString())
|
||||||
);
|
);
|
||||||
|
|
||||||
workspaces = (
|
const workspaces = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
user: req.user._id
|
user: req.user._id
|
||||||
}).populate('workspace')
|
}).populate('workspace')
|
||||||
)
|
)
|
||||||
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||||
.map((m) => m.workspace);
|
.map((m) => m.workspace);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get my workspaces'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspaces
|
workspaces
|
||||||
@@ -179,29 +132,20 @@ export const getOrganizationWorkspaces = async (
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const changeOrganizationName = async (req: Request, res: Response) => {
|
export const changeOrganizationName = async (req: Request, res: Response) => {
|
||||||
let organization;
|
const { organizationId } = req.params;
|
||||||
try {
|
const { name } = req.body;
|
||||||
const { organizationId } = req.params;
|
|
||||||
const { name } = req.body;
|
|
||||||
|
|
||||||
organization = await Organization.findOneAndUpdate(
|
const organization = await Organization.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: organizationId
|
_id: organizationId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name
|
name
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to change organization name'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully changed organization name',
|
message: 'Successfully changed organization name',
|
||||||
@@ -219,20 +163,11 @@ export const getOrganizationIncidentContacts = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let incidentContactsOrg;
|
const { organizationId } = req.params;
|
||||||
try {
|
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
incidentContactsOrg = await IncidentContactOrg.find({
|
const incidentContactsOrg = await IncidentContactOrg.find({
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get organization incident contacts'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
incidentContactsOrg
|
incidentContactsOrg
|
||||||
@@ -249,23 +184,14 @@ export const addOrganizationIncidentContact = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let incidentContactOrg;
|
const { organizationId } = req.params;
|
||||||
try {
|
const { email } = req.body;
|
||||||
const { organizationId } = req.params;
|
|
||||||
const { email } = req.body;
|
|
||||||
|
|
||||||
incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
|
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
|
||||||
{ email, organization: organizationId },
|
{ email, organization: organizationId },
|
||||||
{ email, organization: organizationId },
|
{ email, organization: organizationId },
|
||||||
{ upsert: true, new: true }
|
{ upsert: true, new: true }
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to add incident contact for organization'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
incidentContactOrg
|
incidentContactOrg
|
||||||
@@ -282,22 +208,13 @@ export const deleteOrganizationIncidentContact = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let incidentContactOrg;
|
const { organizationId } = req.params;
|
||||||
try {
|
const { email } = req.body;
|
||||||
const { organizationId } = req.params;
|
|
||||||
const { email } = req.body;
|
|
||||||
|
|
||||||
incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
|
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
|
||||||
email,
|
email,
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete organization incident contact'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully deleted organization incident contact',
|
message: 'Successfully deleted organization incident contact',
|
||||||
@@ -317,41 +234,33 @@ export const createOrganizationPortalSession = async (
|
|||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let session;
|
let session;
|
||||||
try {
|
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
apiVersion: '2022-08-01'
|
||||||
apiVersion: '2022-08-01'
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// check if there is a payment method on file
|
// check if there is a payment method on file
|
||||||
const paymentMethods = await stripe.paymentMethods.list({
|
const paymentMethods = await stripe.paymentMethods.list({
|
||||||
customer: req.organization.customerId,
|
customer: req.organization.customerId,
|
||||||
type: 'card'
|
type: 'card'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (paymentMethods.data.length < 1) {
|
if (paymentMethods.data.length < 1) {
|
||||||
// case: no payment method on file
|
// case: no payment method on file
|
||||||
session = await stripe.checkout.sessions.create({
|
session = await stripe.checkout.sessions.create({
|
||||||
customer: req.organization.customerId,
|
customer: req.organization.customerId,
|
||||||
mode: 'setup',
|
mode: 'setup',
|
||||||
payment_method_types: ['card'],
|
payment_method_types: ['card'],
|
||||||
success_url: (await getSiteURL()) + '/dashboard',
|
success_url: (await getSiteURL()) + '/dashboard',
|
||||||
cancel_url: (await getSiteURL()) + '/dashboard'
|
cancel_url: (await getSiteURL()) + '/dashboard'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
session = await stripe.billingPortal.sessions.create({
|
session = await stripe.billingPortal.sessions.create({
|
||||||
customer: req.organization.customerId,
|
customer: req.organization.customerId,
|
||||||
return_url: (await getSiteURL()) + '/dashboard'
|
return_url: (await getSiteURL()) + '/dashboard'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({ url: session.url });
|
return res.status(200).send({ url: session.url });
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to redirect to organization billing portal'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -364,22 +273,13 @@ export const getOrganizationSubscriptions = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let subscriptions;
|
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||||
try {
|
apiVersion: '2022-08-01'
|
||||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
});
|
||||||
apiVersion: '2022-08-01'
|
|
||||||
});
|
const subscriptions = await stripe.subscriptions.list({
|
||||||
|
customer: req.organization.customerId
|
||||||
subscriptions = await stripe.subscriptions.list({
|
});
|
||||||
customer: req.organization.customerId
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get organization subscriptions'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
subscriptions
|
subscriptions
|
||||||
@@ -425,4 +325,4 @@ export const getOrganizationMembersAndTheirWorkspaces = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return res.json(userToWorkspaceIds);
|
return res.json(userToWorkspaceIds);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
@@ -19,41 +18,32 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../conf
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const emailPasswordReset = async (req: Request, res: Response) => {
|
export const emailPasswordReset = async (req: Request, res: Response) => {
|
||||||
let email: string;
|
const email = req.body.email;
|
||||||
try {
|
|
||||||
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'
|
error: 'Failed to send email verification for password reset'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await TokenService.createToken({
|
const token = await TokenService.createToken({
|
||||||
type: TOKEN_EMAIL_PASSWORD_RESET,
|
type: TOKEN_EMAIL_PASSWORD_RESET,
|
||||||
email
|
email
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendMail({
|
await sendMail({
|
||||||
template: 'passwordReset.handlebars',
|
template: 'passwordReset.handlebars',
|
||||||
subjectLine: 'Infisical password reset',
|
subjectLine: 'Infisical password reset',
|
||||||
recipients: [email],
|
recipients: [email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
email,
|
email,
|
||||||
token,
|
token,
|
||||||
callback_url: (await getSiteURL()) + '/password-reset'
|
callback_url: (await getSiteURL()) + '/password-reset'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to send email for account recovery'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: `Sent an email for account recovery to ${email}`
|
message: `Sent an email for account recovery to ${email}`
|
||||||
@@ -67,40 +57,31 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const emailPasswordResetVerify = async (req: Request, res: Response) => {
|
export const emailPasswordResetVerify = async (req: Request, res: Response) => {
|
||||||
let user, token;
|
const { email, code } = req.body;
|
||||||
try {
|
|
||||||
const { email, code } = req.body;
|
|
||||||
|
|
||||||
user = await User.findOne({ email }).select('+publicKey');
|
const user = await User.findOne({ email }).select('+publicKey');
|
||||||
if (!user || !user?.publicKey) {
|
if (!user || !user?.publicKey) {
|
||||||
// case: user doesn't exist with email [email] or
|
// case: user doesn't exist with email [email] or
|
||||||
// hasn't even completed their account
|
// hasn't even completed their account
|
||||||
return res.status(403).send({
|
return res.status(403).send({
|
||||||
error: 'Failed email verification for password reset'
|
error: 'Failed email verification for password reset'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await TokenService.validateToken({
|
await TokenService.validateToken({
|
||||||
type: TOKEN_EMAIL_PASSWORD_RESET,
|
type: TOKEN_EMAIL_PASSWORD_RESET,
|
||||||
email,
|
email,
|
||||||
token: code
|
token: code
|
||||||
});
|
});
|
||||||
|
|
||||||
// generate temporary password-reset token
|
// generate temporary password-reset token
|
||||||
token = createToken({
|
const token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
userId: user._id.toString()
|
userId: user._id.toString()
|
||||||
},
|
},
|
||||||
expiresIn: await getJwtSignupLifetime(),
|
expiresIn: await getJwtSignupLifetime(),
|
||||||
secret: await getJwtSignupSecret()
|
secret: await getJwtSignupSecret()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed email verification for password reset'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully verified email',
|
message: 'Successfully verified email',
|
||||||
@@ -117,43 +98,35 @@ 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
|
||||||
try {
|
const { clientPublicKey } = req.body;
|
||||||
const { clientPublicKey } = req.body;
|
const user = await User.findOne({
|
||||||
const user = await User.findOne({
|
email: req.user.email
|
||||||
email: req.user.email
|
}).select('+salt +verifier');
|
||||||
}).select('+salt +verifier');
|
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
if (!user) throw new Error('Failed to find user');
|
||||||
|
|
||||||
const server = new jsrp.server();
|
const server = new jsrp.server();
|
||||||
server.init(
|
server.init(
|
||||||
{
|
{
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
verifier: user.verifier
|
verifier: user.verifier
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
// generate server-side public key
|
// generate server-side public key
|
||||||
const serverPublicKey = server.getPublicKey();
|
const serverPublicKey = server.getPublicKey();
|
||||||
|
|
||||||
await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, {
|
await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, {
|
||||||
email: req.user.email,
|
email: req.user.email,
|
||||||
clientPublicKey: clientPublicKey,
|
clientPublicKey: clientPublicKey,
|
||||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||||
}, { upsert: true, returnNewDocument: false })
|
}, { upsert: true, returnNewDocument: false })
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serverPublicKey,
|
serverPublicKey,
|
||||||
salt: user.salt
|
salt: user.salt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed to start change password process'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,80 +138,72 @@ export const srp1 = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const changePassword = async (req: Request, res: Response) => {
|
export const changePassword = async (req: Request, res: Response) => {
|
||||||
try {
|
const {
|
||||||
const {
|
clientProof,
|
||||||
clientProof,
|
protectedKey,
|
||||||
protectedKey,
|
protectedKeyIV,
|
||||||
protectedKeyIV,
|
protectedKeyTag,
|
||||||
protectedKeyTag,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyTag,
|
||||||
encryptedPrivateKeyTag,
|
salt,
|
||||||
salt,
|
verifier
|
||||||
verifier
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
email: req.user.email
|
email: req.user.email
|
||||||
}).select('+salt +verifier');
|
}).select('+salt +verifier');
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
if (!user) throw new Error('Failed to find user');
|
||||||
|
|
||||||
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
|
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
|
||||||
|
|
||||||
if (!loginSRPDetailFromDB) {
|
if (!loginSRPDetailFromDB) {
|
||||||
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = new jsrp.server();
|
const server = new jsrp.server();
|
||||||
server.init(
|
server.init(
|
||||||
{
|
{
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
verifier: user.verifier,
|
verifier: user.verifier,
|
||||||
b: loginSRPDetailFromDB.serverBInt
|
b: loginSRPDetailFromDB.serverBInt
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
|
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
|
||||||
|
|
||||||
// compare server and client shared keys
|
// compare server and client shared keys
|
||||||
if (server.checkClientProof(clientProof)) {
|
if (server.checkClientProof(clientProof)) {
|
||||||
// change password
|
// change password
|
||||||
|
|
||||||
await User.findByIdAndUpdate(
|
await User.findByIdAndUpdate(
|
||||||
req.user._id.toString(),
|
req.user._id.toString(),
|
||||||
{
|
{
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv: encryptedPrivateKeyIV,
|
iv: encryptedPrivateKeyIV,
|
||||||
tag: encryptedPrivateKeyTag,
|
tag: encryptedPrivateKeyTag,
|
||||||
salt,
|
salt,
|
||||||
verifier
|
verifier
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully changed password'
|
message: 'Successfully changed password'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
error: 'Failed to change password. Try again?'
|
error: 'Failed to change password. Try again?'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed to change password. Try again?'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,69 +217,61 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
|
|||||||
// requires verifying [clientProof] as part of second step of SRP protocol
|
// requires verifying [clientProof] as part of second step of SRP protocol
|
||||||
// as initiated in /srp1
|
// as initiated in /srp1
|
||||||
|
|
||||||
try {
|
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } =
|
||||||
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } =
|
req.body;
|
||||||
req.body;
|
const user = await User.findOne({
|
||||||
const user = await User.findOne({
|
email: req.user.email
|
||||||
email: req.user.email
|
}).select('+salt +verifier');
|
||||||
}).select('+salt +verifier');
|
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
if (!user) throw new Error('Failed to find user');
|
||||||
|
|
||||||
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
|
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
|
||||||
|
|
||||||
if (!loginSRPDetailFromDB) {
|
if (!loginSRPDetailFromDB) {
|
||||||
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = new jsrp.server();
|
const server = new jsrp.server();
|
||||||
server.init(
|
server.init(
|
||||||
{
|
{
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
verifier: user.verifier,
|
verifier: user.verifier,
|
||||||
b: loginSRPDetailFromDB.serverBInt
|
b: loginSRPDetailFromDB.serverBInt
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
server.setClientPublicKey(
|
server.setClientPublicKey(
|
||||||
loginSRPDetailFromDB.clientPublicKey
|
loginSRPDetailFromDB.clientPublicKey
|
||||||
);
|
);
|
||||||
|
|
||||||
// compare server and client shared keys
|
// compare server and client shared keys
|
||||||
if (server.checkClientProof(clientProof)) {
|
if (server.checkClientProof(clientProof)) {
|
||||||
// create new or replace backup private key
|
// create new or replace backup private key
|
||||||
|
|
||||||
const backupPrivateKey = await BackupPrivateKey.findOneAndUpdate(
|
const backupPrivateKey = await BackupPrivateKey.findOneAndUpdate(
|
||||||
{ user: req.user._id },
|
{ user: req.user._id },
|
||||||
{
|
{
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv,
|
iv,
|
||||||
tag,
|
tag,
|
||||||
salt,
|
salt,
|
||||||
verifier
|
verifier
|
||||||
},
|
},
|
||||||
{ upsert: true, new: true }
|
{ upsert: true, new: true }
|
||||||
).select('+user, encryptedPrivateKey');
|
).select('+user, encryptedPrivateKey');
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully updated backup private key',
|
message: 'Successfully updated backup private key',
|
||||||
backupPrivateKey
|
backupPrivateKey
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: 'Failed to update backup private key'
|
message: 'Failed to update backup private key'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to update backup private key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,20 +281,11 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getBackupPrivateKey = async (req: Request, res: Response) => {
|
export const getBackupPrivateKey = async (req: Request, res: Response) => {
|
||||||
let backupPrivateKey;
|
const backupPrivateKey = await BackupPrivateKey.findOne({
|
||||||
try {
|
user: req.user._id
|
||||||
backupPrivateKey = await BackupPrivateKey.findOne({
|
}).select('+encryptedPrivateKey +iv +tag');
|
||||||
user: req.user._id
|
|
||||||
}).select('+encryptedPrivateKey +iv +tag');
|
|
||||||
|
|
||||||
if (!backupPrivateKey) throw new Error('Failed to find backup private key');
|
if (!backupPrivateKey) throw new Error('Failed to find backup private key');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get backup private key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
backupPrivateKey
|
backupPrivateKey
|
||||||
@@ -345,44 +293,36 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const resetPassword = async (req: Request, res: Response) => {
|
export const resetPassword = async (req: Request, res: Response) => {
|
||||||
try {
|
const {
|
||||||
const {
|
protectedKey,
|
||||||
protectedKey,
|
protectedKeyIV,
|
||||||
protectedKeyIV,
|
protectedKeyTag,
|
||||||
protectedKeyTag,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyTag,
|
||||||
encryptedPrivateKeyTag,
|
salt,
|
||||||
salt,
|
verifier,
|
||||||
verifier,
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
await User.findByIdAndUpdate(
|
await User.findByIdAndUpdate(
|
||||||
req.user._id.toString(),
|
req.user._id.toString(),
|
||||||
{
|
{
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv: encryptedPrivateKeyIV,
|
iv: encryptedPrivateKeyIV,
|
||||||
tag: encryptedPrivateKeyTag,
|
tag: encryptedPrivateKeyTag,
|
||||||
salt,
|
salt,
|
||||||
verifier
|
verifier
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get backup private key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully reset password'
|
message: 'Successfully reset password'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { Key, Secret } from '../../models';
|
import { Key, Secret } from '../../models';
|
||||||
import {
|
import {
|
||||||
@@ -37,66 +36,56 @@ interface PushSecret {
|
|||||||
*/
|
*/
|
||||||
export const pushSecrets = async (req: Request, res: Response) => {
|
export const pushSecrets = async (req: Request, res: Response) => {
|
||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
|
let { secrets }: { secrets: PushSecret[] } = req.body;
|
||||||
|
const { keys, environment, channel } = req.body;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
try {
|
// validate environment
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const workspaceEnvs = req.membership.workspace.environments;
|
||||||
let { secrets }: { secrets: PushSecret[] } = req.body;
|
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||||
const { keys, environment, channel } = req.body;
|
throw new Error('Failed to validate environment');
|
||||||
const { workspaceId } = req.params;
|
}
|
||||||
|
|
||||||
// validate environment
|
// sanitize secrets
|
||||||
const workspaceEnvs = req.membership.workspace.environments;
|
secrets = secrets.filter(
|
||||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
(s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== ''
|
||||||
throw new Error('Failed to validate environment');
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// sanitize secrets
|
await push({
|
||||||
secrets = secrets.filter(
|
userId: req.user._id,
|
||||||
(s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== ''
|
workspaceId,
|
||||||
);
|
environment,
|
||||||
|
secrets
|
||||||
|
});
|
||||||
|
|
||||||
await push({
|
await pushKeys({
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
keys
|
||||||
secrets
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets pushed',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await pushKeys({
|
// trigger event - push secrets
|
||||||
userId: req.user._id,
|
EventService.handleEvent({
|
||||||
workspaceId,
|
event: eventPushSecrets({
|
||||||
keys
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
});
|
environment
|
||||||
|
})
|
||||||
|
});
|
||||||
if (postHogClient) {
|
|
||||||
postHogClient.capture({
|
|
||||||
event: 'secrets pushed',
|
|
||||||
distinctId: req.user.email,
|
|
||||||
properties: {
|
|
||||||
numberOfSecrets: secrets.length,
|
|
||||||
environment,
|
|
||||||
workspaceId,
|
|
||||||
channel: channel ? channel : 'cli'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// trigger event - push secrets
|
|
||||||
EventService.handleEvent({
|
|
||||||
event: eventPushSecrets({
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to upload workspace secrets'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully uploaded workspace secrets'
|
message: 'Successfully uploaded workspace secrets'
|
||||||
@@ -111,59 +100,50 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const pullSecrets = async (req: Request, res: Response) => {
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
let secrets;
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
let key;
|
const environment: string = req.query.environment as string;
|
||||||
try {
|
const channel: string = req.query.channel as string;
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const { workspaceId } = req.params;
|
||||||
const environment: string = req.query.environment as string;
|
|
||||||
const channel: string = req.query.channel as string;
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
// validate environment
|
// validate environment
|
||||||
const workspaceEnvs = req.membership.workspace.environments;
|
const workspaceEnvs = req.membership.workspace.environments;
|
||||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||||
throw new Error('Failed to validate environment');
|
throw new Error('Failed to validate environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets = await pull({
|
let secrets = await pull({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user._id.toString(),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
channel: channel ? channel : 'cli',
|
channel: channel ? channel : 'cli',
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
key = await Key.findOne({
|
const key = await Key.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
receiver: req.user._id
|
receiver: req.user._id
|
||||||
})
|
})
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.populate('sender', '+publicKey');
|
.populate('sender', '+publicKey');
|
||||||
|
|
||||||
if (channel !== 'cli') {
|
if (channel !== 'cli') {
|
||||||
secrets = reformatPullSecrets({ secrets });
|
// FIX: Fix this any
|
||||||
}
|
secrets = reformatPullSecrets({ secrets }) as any;
|
||||||
|
}
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
// capture secrets pushed event in production
|
// capture secrets pushed event in production
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
distinctId: req.user.email,
|
distinctId: req.user.email,
|
||||||
event: 'secrets pulled',
|
event: 'secrets pulled',
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel ? channel : 'cli'
|
channel: channel ? channel : 'cli'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to pull workspace secrets'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets,
|
secrets,
|
||||||
@@ -180,61 +160,51 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||||
let secrets;
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
let key;
|
const environment: string = req.query.environment as string;
|
||||||
try {
|
const channel: string = req.query.channel as string;
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const { workspaceId } = req.params;
|
||||||
const environment: string = req.query.environment as string;
|
|
||||||
const channel: string = req.query.channel as string;
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
// validate environment
|
// validate environment
|
||||||
const workspaceEnvs = req.membership.workspace.environments;
|
const workspaceEnvs = req.membership.workspace.environments;
|
||||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||||
throw new Error('Failed to validate environment');
|
throw new Error('Failed to validate environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets = await pull({
|
const secrets = await pull({
|
||||||
userId: req.serviceToken.user._id.toString(),
|
userId: req.serviceToken.user._id.toString(),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
channel: 'cli',
|
channel: 'cli',
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
key = {
|
const key = {
|
||||||
encryptedKey: req.serviceToken.encryptedKey,
|
encryptedKey: req.serviceToken.encryptedKey,
|
||||||
nonce: req.serviceToken.nonce,
|
nonce: req.serviceToken.nonce,
|
||||||
sender: {
|
sender: {
|
||||||
publicKey: req.serviceToken.publicKey
|
publicKey: req.serviceToken.publicKey
|
||||||
},
|
},
|
||||||
receiver: req.serviceToken.user,
|
receiver: req.serviceToken.user,
|
||||||
workspace: req.serviceToken.workspace
|
workspace: req.serviceToken.workspace
|
||||||
};
|
};
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
// capture secrets pulled event in production
|
// capture secrets pulled event in production
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
distinctId: req.serviceToken.user.email,
|
distinctId: req.serviceToken.user.email,
|
||||||
event: 'secrets pulled',
|
event: 'secrets pulled',
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel ? channel : 'cli'
|
channel: channel ? channel : 'cli'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.serviceToken.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to pull workspace secrets'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets: reformatPullSecrets({ secrets }),
|
secrets: reformatPullSecrets({ secrets }),
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { User } from '../../models';
|
import { User } from '../../models';
|
||||||
import {
|
import {
|
||||||
sendEmailVerification,
|
sendEmailVerification,
|
||||||
@@ -17,29 +16,20 @@ import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpC
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const beginEmailSignup = async (req: Request, res: Response) => {
|
export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||||
let email: string;
|
const email = req.body.email;
|
||||||
try {
|
|
||||||
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 code for complete account'
|
error: 'Failed to send email verification code for complete account'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send send verification email
|
||||||
|
await sendEmailVerification({ email });
|
||||||
|
|
||||||
// send send verification email
|
|
||||||
await sendEmailVerification({ email });
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed to send email verification code'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: `Sent an email verification code to ${email}`
|
message: `Sent an email verification code to ${email}`
|
||||||
});
|
});
|
||||||
@@ -54,55 +44,47 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const verifyEmailSignup = async (req: Request, res: Response) => {
|
export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||||
let user, token;
|
let user, token;
|
||||||
try {
|
const { email, code } = req.body;
|
||||||
const { email, code } = req.body;
|
|
||||||
|
|
||||||
// initialize user account
|
// initialize user account
|
||||||
user = await User.findOne({ email }).select('+publicKey');
|
user = await User.findOne({ email }).select('+publicKey');
|
||||||
if (user && user?.publicKey) {
|
if (user && user?.publicKey) {
|
||||||
// case: user has already completed account
|
// case: user has already completed account
|
||||||
return res.status(403).send({
|
return res.status(403).send({
|
||||||
error: 'Failed email verification for complete user'
|
error: 'Failed email verification for complete user'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await getInviteOnlySignup()) {
|
if (await getInviteOnlySignup()) {
|
||||||
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
|
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
|
||||||
const userCount = await User.countDocuments({})
|
const userCount = await User.countDocuments({})
|
||||||
if (userCount != 0) {
|
if (userCount != 0) {
|
||||||
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
|
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify email
|
// verify email
|
||||||
if (await getSmtpConfigured()) {
|
if (await getSmtpConfigured()) {
|
||||||
await checkEmailVerification({
|
await checkEmailVerification({
|
||||||
email,
|
email,
|
||||||
code
|
code
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await new User({
|
user = await new User({
|
||||||
email
|
email
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate temporary signup token
|
// generate temporary signup token
|
||||||
token = createToken({
|
token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
userId: user._id.toString()
|
userId: user._id.toString()
|
||||||
},
|
},
|
||||||
expiresIn: await getJwtSignupLifetime(),
|
expiresIn: await getJwtSignupLifetime(),
|
||||||
secret: await getJwtSignupSecret()
|
secret: await getJwtSignupSecret()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed email verification'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfuly verified email',
|
message: 'Successfuly verified email',
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { getStripeSecretKey, getStripeWebhookSecret } from '../../config';
|
import { getStripeSecretKey, getStripeWebhookSecret } from '../../config';
|
||||||
|
|
||||||
@@ -10,26 +9,17 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../config';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const handleWebhook = async (req: Request, res: Response) => {
|
export const handleWebhook = async (req: Request, res: Response) => {
|
||||||
let event;
|
// check request for valid stripe signature
|
||||||
try {
|
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||||
// check request for valid stripe signature
|
apiVersion: '2022-08-01'
|
||||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
});
|
||||||
apiVersion: '2022-08-01'
|
|
||||||
});
|
|
||||||
|
|
||||||
const sig = req.headers['stripe-signature'] as string;
|
const sig = req.headers['stripe-signature'] as string;
|
||||||
event = stripe.webhooks.constructEvent(
|
const event = stripe.webhooks.constructEvent(
|
||||||
req.body,
|
req.body,
|
||||||
sig,
|
sig,
|
||||||
await getStripeWebhookSecret()
|
await getStripeWebhookSecret()
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed to process webhook'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case '':
|
case '':
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { UserAction } from '../../models';
|
import { UserAction } from '../../models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,28 +10,19 @@ import { UserAction } from '../../models';
|
|||||||
export const addUserAction = async (req: Request, res: Response) => {
|
export const addUserAction = async (req: Request, res: Response) => {
|
||||||
// add/record new action [action] for user with id [req.user._id]
|
// add/record new action [action] for user with id [req.user._id]
|
||||||
|
|
||||||
let userAction;
|
const { action } = req.body;
|
||||||
try {
|
|
||||||
const { action } = req.body;
|
|
||||||
|
|
||||||
userAction = await UserAction.findOneAndUpdate(
|
const userAction = await UserAction.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
action
|
action
|
||||||
},
|
},
|
||||||
{ user: req.user._id, action },
|
{ user: req.user._id, action },
|
||||||
{
|
{
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to record user action'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully recorded user action',
|
message: 'Successfully recorded user action',
|
||||||
@@ -48,21 +38,12 @@ export const addUserAction = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const getUserAction = async (req: Request, res: Response) => {
|
export const getUserAction = async (req: Request, res: Response) => {
|
||||||
// get user action [action] for user with id [req.user._id]
|
// get user action [action] for user with id [req.user._id]
|
||||||
let userAction;
|
const action: string = req.query.action as string;
|
||||||
try {
|
|
||||||
const action: string = req.query.action as string;
|
|
||||||
|
|
||||||
userAction = await UserAction.findOne({
|
const userAction = await UserAction.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
action
|
action
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get user action'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
userAction
|
userAction
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import * as Sentry from "@sentry/node";
|
|
||||||
import {
|
import {
|
||||||
Workspace,
|
Workspace,
|
||||||
Membership,
|
Membership,
|
||||||
@@ -24,27 +23,18 @@ import { ADMIN } from "../../variables";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
||||||
let publicKeys;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
publicKeys = (
|
const publicKeys = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
}).populate<{ user: IUser }>("user", "publicKey")
|
}).populate<{ user: IUser }>("user", "publicKey")
|
||||||
).map((member) => {
|
).map((member) => {
|
||||||
return {
|
return {
|
||||||
publicKey: member.user.publicKey,
|
publicKey: member.user.publicKey,
|
||||||
userId: member.user._id,
|
userId: member.user._id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace member public keys",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
publicKeys,
|
publicKeys,
|
||||||
@@ -58,20 +48,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||||
let users;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
users = await Membership.find({
|
const users = await Membership.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
}).populate("user", "+publicKey");
|
}).populate("user", "+publicKey");
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace members",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
users,
|
users,
|
||||||
@@ -85,20 +66,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaces = async (req: Request, res: Response) => {
|
export const getWorkspaces = async (req: Request, res: Response) => {
|
||||||
let workspaces;
|
const workspaces = (
|
||||||
try {
|
await Membership.find({
|
||||||
workspaces = (
|
user: req.user._id,
|
||||||
await Membership.find({
|
}).populate("workspace")
|
||||||
user: req.user._id,
|
).map((m) => m.workspace);
|
||||||
}).populate("workspace")
|
|
||||||
).map((m) => m.workspace);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspaces",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspaces,
|
workspaces,
|
||||||
@@ -112,20 +84,11 @@ export const getWorkspaces = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspace = async (req: Request, res: Response) => {
|
export const getWorkspace = async (req: Request, res: Response) => {
|
||||||
let workspace;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
workspace = await Workspace.findOne({
|
const workspace = await Workspace.findOne({
|
||||||
_id: workspaceId,
|
_id: workspaceId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspace,
|
workspace,
|
||||||
@@ -140,43 +103,34 @@ export const getWorkspace = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createWorkspace = async (req: Request, res: Response) => {
|
export const createWorkspace = async (req: Request, res: Response) => {
|
||||||
let workspace;
|
const { workspaceName, organizationId } = req.body;
|
||||||
try {
|
|
||||||
const { workspaceName, organizationId } = req.body;
|
|
||||||
|
|
||||||
// validate organization membership
|
// validate organization membership
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
organization: organizationId,
|
organization: organizationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg) {
|
if (!membershipOrg) {
|
||||||
throw new Error("Failed to validate organization membership");
|
throw new Error("Failed to validate organization membership");
|
||||||
}
|
|
||||||
|
|
||||||
if (workspaceName.length < 1) {
|
|
||||||
throw new Error("Workspace names must be at least 1-character long");
|
|
||||||
}
|
|
||||||
|
|
||||||
// create workspace and add user as member
|
|
||||||
workspace = await create({
|
|
||||||
name: workspaceName,
|
|
||||||
organizationId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await addMemberships({
|
|
||||||
userIds: [req.user._id],
|
|
||||||
workspaceId: workspace._id.toString(),
|
|
||||||
roles: [ADMIN],
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to create workspace",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (workspaceName.length < 1) {
|
||||||
|
throw new Error("Workspace names must be at least 1-character long");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create workspace and add user as member
|
||||||
|
const workspace = await create({
|
||||||
|
name: workspaceName,
|
||||||
|
organizationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await addMemberships({
|
||||||
|
userIds: [req.user._id],
|
||||||
|
workspaceId: workspace._id.toString(),
|
||||||
|
roles: [ADMIN],
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspace,
|
workspace,
|
||||||
});
|
});
|
||||||
@@ -189,20 +143,12 @@ export const createWorkspace = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteWorkspace = async (req: Request, res: Response) => {
|
export const deleteWorkspace = async (req: Request, res: Response) => {
|
||||||
try {
|
const { workspaceId } = req.params;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
// delete workspace
|
// delete workspace
|
||||||
await deleteWork({
|
await deleteWork({
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to delete workspace",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully deleted workspace",
|
message: "Successfully deleted workspace",
|
||||||
@@ -216,29 +162,20 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const changeWorkspaceName = async (req: Request, res: Response) => {
|
export const changeWorkspaceName = async (req: Request, res: Response) => {
|
||||||
let workspace;
|
const { workspaceId } = req.params;
|
||||||
try {
|
const { name } = req.body;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
const { name } = req.body;
|
|
||||||
|
|
||||||
workspace = await Workspace.findOneAndUpdate(
|
const workspace = await Workspace.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: workspaceId,
|
_id: workspaceId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to change workspace name",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully changed workspace name",
|
message: "Successfully changed workspace name",
|
||||||
@@ -253,20 +190,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
||||||
let integrations;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
integrations = await Integration.find({
|
const integrations = await Integration.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace integrations",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrations,
|
integrations,
|
||||||
@@ -283,20 +211,11 @@ export const getWorkspaceIntegrationAuthorizations = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let authorizations;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
authorizations = await IntegrationAuth.find({
|
const authorizations = await IntegrationAuth.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace integration authorizations",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
authorizations,
|
authorizations,
|
||||||
@@ -313,21 +232,12 @@ export const getWorkspaceServiceTokens = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let serviceTokens;
|
const { workspaceId } = req.params;
|
||||||
try {
|
// ?? FIX.
|
||||||
const { workspaceId } = req.params;
|
const serviceTokens = await ServiceToken.find({
|
||||||
// ?? FIX.
|
user: req.user._id,
|
||||||
serviceTokens = await ServiceToken.find({
|
workspace: workspaceId,
|
||||||
user: req.user._id,
|
});
|
||||||
workspace: workspaceId,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace service tokens",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokens,
|
serviceTokens,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
@@ -14,18 +13,9 @@ import { getSaltRounds } from '../../config';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getAPIKeyData = async (req: Request, res: Response) => {
|
export const getAPIKeyData = async (req: Request, res: Response) => {
|
||||||
let apiKeyData;
|
const apiKeyData = await APIKeyData.find({
|
||||||
try {
|
user: req.user._id
|
||||||
apiKeyData = await APIKeyData.find({
|
});
|
||||||
user: req.user._id
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get API key data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
apiKeyData
|
apiKeyData
|
||||||
@@ -38,39 +28,30 @@ export const getAPIKeyData = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createAPIKeyData = async (req: Request, res: Response) => {
|
export const createAPIKeyData = async (req: Request, res: Response) => {
|
||||||
let apiKey, apiKeyData;
|
const { name, expiresIn } = req.body;
|
||||||
try {
|
|
||||||
const { name, expiresIn } = req.body;
|
|
||||||
|
|
||||||
const secret = crypto.randomBytes(16).toString('hex');
|
|
||||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
|
||||||
|
|
||||||
const expiresAt = new Date();
|
|
||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
|
||||||
|
|
||||||
apiKeyData = await new APIKeyData({
|
|
||||||
name,
|
|
||||||
lastUsed: new Date(),
|
|
||||||
expiresAt,
|
|
||||||
user: req.user._id,
|
|
||||||
secretHash
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
// return api key data without sensitive data
|
|
||||||
apiKeyData = await APIKeyData.findById(apiKeyData._id);
|
|
||||||
|
|
||||||
if (!apiKeyData) throw new Error('Failed to find API key data');
|
|
||||||
|
|
||||||
apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to API key data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const secret = crypto.randomBytes(16).toString('hex');
|
||||||
|
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||||
|
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
|
||||||
|
let apiKeyData = await new APIKeyData({
|
||||||
|
name,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
expiresAt,
|
||||||
|
user: req.user._id,
|
||||||
|
secretHash
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
// return api key data without sensitive data
|
||||||
|
// FIX: fix this any
|
||||||
|
apiKeyData = await APIKeyData.findById(apiKeyData._id) as any
|
||||||
|
|
||||||
|
if (!apiKeyData) throw new Error('Failed to find API key data');
|
||||||
|
|
||||||
|
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
apiKey,
|
apiKey,
|
||||||
apiKeyData
|
apiKeyData
|
||||||
@@ -84,21 +65,10 @@ export const createAPIKeyData = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteAPIKeyData = async (req: Request, res: Response) => {
|
export const deleteAPIKeyData = async (req: Request, res: Response) => {
|
||||||
let apiKeyData;
|
const { apiKeyDataId } = req.params;
|
||||||
try {
|
const apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId);
|
||||||
const { apiKeyDataId } = req.params;
|
|
||||||
|
|
||||||
apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete API key data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
apiKeyData
|
apiKeyData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
import { User, LoginSRPDetail } from '../../models';
|
import { User, LoginSRPDetail } from '../../models';
|
||||||
@@ -35,47 +34,40 @@ declare module 'jsonwebtoken' {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const login1 = async (req: Request, res: Response) => {
|
export const login1 = async (req: Request, res: Response) => {
|
||||||
try {
|
const {
|
||||||
const {
|
email,
|
||||||
email,
|
clientPublicKey
|
||||||
clientPublicKey
|
}: { email: string; clientPublicKey: string } = req.body;
|
||||||
}: { email: string; clientPublicKey: string } = req.body;
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
email
|
email
|
||||||
}).select('+salt +verifier');
|
}).select('+salt +verifier');
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
if (!user) throw new Error('Failed to find user');
|
||||||
|
|
||||||
const server = new jsrp.server();
|
const server = new jsrp.server();
|
||||||
server.init(
|
server.init(
|
||||||
{
|
{
|
||||||
salt: user.salt,
|
salt: user.salt,
|
||||||
verifier: user.verifier
|
verifier: user.verifier
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
// generate server-side public key
|
// generate server-side public key
|
||||||
const serverPublicKey = server.getPublicKey();
|
const serverPublicKey = server.getPublicKey();
|
||||||
|
|
||||||
await LoginSRPDetail.findOneAndReplace({ email: email }, {
|
await LoginSRPDetail.findOneAndReplace({ email: email }, {
|
||||||
email: email,
|
email: email,
|
||||||
clientPublicKey: clientPublicKey,
|
clientPublicKey: clientPublicKey,
|
||||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||||
}, { upsert: true, returnNewDocument: false });
|
}, { upsert: true, returnNewDocument: false });
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serverPublicKey,
|
serverPublicKey,
|
||||||
salt: user.salt
|
salt: user.salt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to start authentication process'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,149 +78,140 @@ export const login1 = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const login2 = async (req: Request, res: Response) => {
|
export const login2 = async (req: Request, res: Response) => {
|
||||||
try {
|
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
|
||||||
|
|
||||||
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
|
const { email, clientProof } = req.body;
|
||||||
|
const user = await User.findOne({
|
||||||
|
email
|
||||||
|
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
|
||||||
|
|
||||||
const { email, clientProof } = req.body;
|
if (!user) throw new Error('Failed to find user');
|
||||||
const user = await User.findOne({
|
|
||||||
email
|
|
||||||
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
|
|
||||||
|
|
||||||
if (!user) throw new Error('Failed to find user');
|
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
|
||||||
|
|
||||||
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
|
if (!loginSRPDetail) {
|
||||||
|
return BadRequestError(Error("Failed to find login details for SRP"))
|
||||||
|
}
|
||||||
|
|
||||||
if (!loginSRPDetail) {
|
const server = new jsrp.server();
|
||||||
return BadRequestError(Error("Failed to find login details for SRP"))
|
server.init(
|
||||||
}
|
{
|
||||||
|
salt: user.salt,
|
||||||
|
verifier: user.verifier,
|
||||||
|
b: loginSRPDetail.serverBInt
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
|
||||||
|
|
||||||
const server = new jsrp.server();
|
// compare server and client shared keys
|
||||||
server.init(
|
if (server.checkClientProof(clientProof)) {
|
||||||
{
|
|
||||||
salt: user.salt,
|
|
||||||
verifier: user.verifier,
|
|
||||||
b: loginSRPDetail.serverBInt
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
|
|
||||||
|
|
||||||
// compare server and client shared keys
|
if (user.isMfaEnabled) {
|
||||||
if (server.checkClientProof(clientProof)) {
|
// case: user has MFA enabled
|
||||||
|
|
||||||
if (user.isMfaEnabled) {
|
// generate temporary MFA token
|
||||||
// case: user has MFA enabled
|
const token = createToken({
|
||||||
|
payload: {
|
||||||
// generate temporary MFA token
|
userId: user._id.toString()
|
||||||
const token = createToken({
|
},
|
||||||
payload: {
|
expiresIn: await getJwtMfaLifetime(),
|
||||||
userId: user._id.toString()
|
secret: await getJwtMfaSecret()
|
||||||
},
|
|
||||||
expiresIn: await getJwtMfaLifetime(),
|
|
||||||
secret: await getJwtMfaSecret()
|
|
||||||
});
|
|
||||||
|
|
||||||
const code = await TokenService.createToken({
|
|
||||||
type: TOKEN_EMAIL_MFA,
|
|
||||||
email
|
|
||||||
});
|
|
||||||
|
|
||||||
// send MFA code [code] to [email]
|
|
||||||
await sendMail({
|
|
||||||
template: 'emailMfa.handlebars',
|
|
||||||
subjectLine: 'Infisical MFA code',
|
|
||||||
recipients: [email],
|
|
||||||
substitutions: {
|
|
||||||
code
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
mfaEnabled: true,
|
|
||||||
token
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await checkUserDevice({
|
|
||||||
user,
|
|
||||||
ip: req.ip,
|
|
||||||
userAgent: req.headers['user-agent'] ?? ''
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// issue tokens
|
const code = await TokenService.createToken({
|
||||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
type: TOKEN_EMAIL_MFA,
|
||||||
|
email
|
||||||
// store (refresh) token in httpOnly cookie
|
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
|
||||||
httpOnly: true,
|
|
||||||
path: '/',
|
|
||||||
sameSite: 'strict',
|
|
||||||
secure: await getHttpsEnabled()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// case: user does not have MFA enablgged
|
// send MFA code [code] to [email]
|
||||||
// return (access) token in response
|
await sendMail({
|
||||||
|
template: 'emailMfa.handlebars',
|
||||||
interface ResponseData {
|
subjectLine: 'Infisical MFA code',
|
||||||
mfaEnabled: boolean;
|
recipients: [email],
|
||||||
encryptionVersion: any;
|
substitutions: {
|
||||||
protectedKey?: string;
|
code
|
||||||
protectedKeyIV?: string;
|
}
|
||||||
protectedKeyTag?: string;
|
|
||||||
token: string;
|
|
||||||
publicKey?: string;
|
|
||||||
encryptedPrivateKey?: string;
|
|
||||||
iv?: string;
|
|
||||||
tag?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: ResponseData = {
|
|
||||||
mfaEnabled: false,
|
|
||||||
encryptionVersion: user.encryptionVersion,
|
|
||||||
token: tokens.token,
|
|
||||||
publicKey: user.publicKey,
|
|
||||||
encryptedPrivateKey: user.encryptedPrivateKey,
|
|
||||||
iv: user.iv,
|
|
||||||
tag: user.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
user?.protectedKey &&
|
|
||||||
user?.protectedKeyIV &&
|
|
||||||
user?.protectedKeyTag
|
|
||||||
) {
|
|
||||||
response.protectedKey = user.protectedKey;
|
|
||||||
response.protectedKeyIV = user.protectedKeyIV
|
|
||||||
response.protectedKeyTag = user.protectedKeyTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginAction = await EELogService.createAction({
|
|
||||||
name: ACTION_LOGIN,
|
|
||||||
userId: user._id
|
|
||||||
});
|
});
|
||||||
|
|
||||||
loginAction && await EELogService.createLog({
|
return res.status(200).send({
|
||||||
userId: user._id,
|
mfaEnabled: true,
|
||||||
actions: [loginAction],
|
token
|
||||||
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
|
||||||
ipAddress: req.ip
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(400).send({
|
await checkUserDevice({
|
||||||
message: 'Failed to authenticate. Try again?'
|
user,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// issue tokens
|
||||||
|
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||||
|
|
||||||
|
// store (refresh) token in httpOnly cookie
|
||||||
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: '/',
|
||||||
|
sameSite: 'strict',
|
||||||
|
secure: await getHttpsEnabled()
|
||||||
|
});
|
||||||
|
|
||||||
|
// case: user does not have MFA enablgged
|
||||||
|
// return (access) token in response
|
||||||
|
|
||||||
|
interface ResponseData {
|
||||||
|
mfaEnabled: boolean;
|
||||||
|
encryptionVersion: any;
|
||||||
|
protectedKey?: string;
|
||||||
|
protectedKeyIV?: string;
|
||||||
|
protectedKeyTag?: string;
|
||||||
|
token: string;
|
||||||
|
publicKey?: string;
|
||||||
|
encryptedPrivateKey?: string;
|
||||||
|
iv?: string;
|
||||||
|
tag?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response: ResponseData = {
|
||||||
|
mfaEnabled: false,
|
||||||
|
encryptionVersion: user.encryptionVersion,
|
||||||
|
token: tokens.token,
|
||||||
|
publicKey: user.publicKey,
|
||||||
|
encryptedPrivateKey: user.encryptedPrivateKey,
|
||||||
|
iv: user.iv,
|
||||||
|
tag: user.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
user?.protectedKey &&
|
||||||
|
user?.protectedKeyIV &&
|
||||||
|
user?.protectedKeyTag
|
||||||
|
) {
|
||||||
|
response.protectedKey = user.protectedKey;
|
||||||
|
response.protectedKeyIV = user.protectedKeyIV
|
||||||
|
response.protectedKeyTag = user.protectedKeyTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginAction = await EELogService.createAction({
|
||||||
|
name: ACTION_LOGIN,
|
||||||
|
userId: user._id
|
||||||
|
});
|
||||||
|
|
||||||
|
loginAction && await EELogService.createLog({
|
||||||
|
userId: user._id,
|
||||||
|
actions: [loginAction],
|
||||||
|
channel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||||
|
ipAddress: req.ip
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send(response);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
} catch (err) {
|
return res.status(400).send({
|
||||||
Sentry.setUser(null);
|
message: 'Failed to authenticate. Try again?'
|
||||||
Sentry.captureException(err);
|
});
|
||||||
return res.status(400).send({
|
}
|
||||||
message: 'Failed to authenticate. Try again?'
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,30 +220,22 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const sendMfaToken = async (req: Request, res: Response) => {
|
export const sendMfaToken = async (req: Request, res: Response) => {
|
||||||
try {
|
const { email } = req.body;
|
||||||
const { email } = req.body;
|
|
||||||
|
|
||||||
const code = await TokenService.createToken({
|
const code = await TokenService.createToken({
|
||||||
type: TOKEN_EMAIL_MFA,
|
type: TOKEN_EMAIL_MFA,
|
||||||
email
|
email
|
||||||
});
|
});
|
||||||
|
|
||||||
// send MFA code [code] to [email]
|
// send MFA code [code] to [email]
|
||||||
await sendMail({
|
await sendMail({
|
||||||
template: 'emailMfa.handlebars',
|
template: 'emailMfa.handlebars',
|
||||||
subjectLine: 'Infisical MFA code',
|
subjectLine: 'Infisical MFA code',
|
||||||
recipients: [email],
|
recipients: [email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
code
|
code
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to send MFA code'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully sent new MFA code'
|
message: 'Successfully sent new MFA code'
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
import {
|
||||||
Secret,
|
Secret,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
@@ -25,30 +24,22 @@ export const createWorkspaceEnvironment = async (
|
|||||||
) => {
|
) => {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
const { environmentName, environmentSlug } = req.body;
|
const { environmentName, environmentSlug } = req.body;
|
||||||
try {
|
const workspace = await Workspace.findById(workspaceId).exec();
|
||||||
const workspace = await Workspace.findById(workspaceId).exec();
|
if (
|
||||||
if (
|
!workspace ||
|
||||||
!workspace ||
|
workspace?.environments.find(
|
||||||
workspace?.environments.find(
|
({ name, slug }) => slug === environmentSlug || environmentName === name
|
||||||
({ name, slug }) => slug === environmentSlug || environmentName === name
|
)
|
||||||
)
|
) {
|
||||||
) {
|
throw new Error('Failed to create workspace environment');
|
||||||
throw new Error('Failed to create workspace environment');
|
|
||||||
}
|
|
||||||
|
|
||||||
workspace?.environments.push({
|
|
||||||
name: environmentName,
|
|
||||||
slug: environmentSlug.toLowerCase(),
|
|
||||||
});
|
|
||||||
await workspace.save();
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to create new workspace environment',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workspace?.environments.push({
|
||||||
|
name: environmentName,
|
||||||
|
slug: environmentSlug.toLowerCase(),
|
||||||
|
});
|
||||||
|
await workspace.save();
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully created new environment',
|
message: 'Successfully created new environment',
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
@@ -72,75 +63,67 @@ export const renameWorkspaceEnvironment = async (
|
|||||||
) => {
|
) => {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
|
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
|
||||||
try {
|
// user should pass both new slug and env name
|
||||||
// user should pass both new slug and env name
|
if (!environmentSlug || !environmentName) {
|
||||||
if (!environmentSlug || !environmentName) {
|
throw new Error('Invalid environment given.');
|
||||||
throw new Error('Invalid environment given.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// atomic update the env to avoid conflict
|
|
||||||
const workspace = await Workspace.findById(workspaceId).exec();
|
|
||||||
if (!workspace) {
|
|
||||||
throw new Error('Failed to create workspace environment');
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEnvExist = workspace.environments.some(
|
|
||||||
({ name, slug }) =>
|
|
||||||
slug !== oldEnvironmentSlug &&
|
|
||||||
(name === environmentName || slug === environmentSlug)
|
|
||||||
);
|
|
||||||
if (isEnvExist) {
|
|
||||||
throw new Error('Invalid environment given');
|
|
||||||
}
|
|
||||||
|
|
||||||
const envIndex = workspace?.environments.findIndex(
|
|
||||||
({ slug }) => slug === oldEnvironmentSlug
|
|
||||||
);
|
|
||||||
if (envIndex === -1) {
|
|
||||||
throw new Error('Invalid environment given');
|
|
||||||
}
|
|
||||||
|
|
||||||
workspace.environments[envIndex].name = environmentName;
|
|
||||||
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();
|
|
||||||
|
|
||||||
await workspace.save();
|
|
||||||
await Secret.updateMany(
|
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
|
||||||
{ environment: environmentSlug }
|
|
||||||
);
|
|
||||||
await SecretVersion.updateMany(
|
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
|
||||||
{ environment: environmentSlug }
|
|
||||||
);
|
|
||||||
await ServiceToken.updateMany(
|
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
|
||||||
{ environment: environmentSlug }
|
|
||||||
);
|
|
||||||
await ServiceTokenData.updateMany(
|
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
|
||||||
{ environment: environmentSlug }
|
|
||||||
);
|
|
||||||
await Integration.updateMany(
|
|
||||||
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
|
||||||
{ environment: environmentSlug }
|
|
||||||
);
|
|
||||||
await Membership.updateMany(
|
|
||||||
{
|
|
||||||
workspace: workspaceId,
|
|
||||||
"deniedPermissions.environmentSlug": oldEnvironmentSlug
|
|
||||||
},
|
|
||||||
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
|
|
||||||
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
|
|
||||||
)
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to update workspace environment',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// atomic update the env to avoid conflict
|
||||||
|
const workspace = await Workspace.findById(workspaceId).exec();
|
||||||
|
if (!workspace) {
|
||||||
|
throw new Error('Failed to create workspace environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEnvExist = workspace.environments.some(
|
||||||
|
({ name, slug }) =>
|
||||||
|
slug !== oldEnvironmentSlug &&
|
||||||
|
(name === environmentName || slug === environmentSlug)
|
||||||
|
);
|
||||||
|
if (isEnvExist) {
|
||||||
|
throw new Error('Invalid environment given');
|
||||||
|
}
|
||||||
|
|
||||||
|
const envIndex = workspace?.environments.findIndex(
|
||||||
|
({ slug }) => slug === oldEnvironmentSlug
|
||||||
|
);
|
||||||
|
if (envIndex === -1) {
|
||||||
|
throw new Error('Invalid environment given');
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.environments[envIndex].name = environmentName;
|
||||||
|
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();
|
||||||
|
|
||||||
|
await workspace.save();
|
||||||
|
await Secret.updateMany(
|
||||||
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
|
{ environment: environmentSlug }
|
||||||
|
);
|
||||||
|
await SecretVersion.updateMany(
|
||||||
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
|
{ environment: environmentSlug }
|
||||||
|
);
|
||||||
|
await ServiceToken.updateMany(
|
||||||
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
|
{ environment: environmentSlug }
|
||||||
|
);
|
||||||
|
await ServiceTokenData.updateMany(
|
||||||
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
|
{ environment: environmentSlug }
|
||||||
|
);
|
||||||
|
await Integration.updateMany(
|
||||||
|
{ workspace: workspaceId, environment: oldEnvironmentSlug },
|
||||||
|
{ environment: environmentSlug }
|
||||||
|
);
|
||||||
|
await Membership.updateMany(
|
||||||
|
{
|
||||||
|
workspace: workspaceId,
|
||||||
|
"deniedPermissions.environmentSlug": oldEnvironmentSlug
|
||||||
|
},
|
||||||
|
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
|
||||||
|
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully update environment',
|
message: 'Successfully update environment',
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
@@ -163,57 +146,48 @@ export const deleteWorkspaceEnvironment = async (
|
|||||||
) => {
|
) => {
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
const { environmentSlug } = req.body;
|
const { environmentSlug } = req.body;
|
||||||
try {
|
// atomic update the env to avoid conflict
|
||||||
// atomic update the env to avoid conflict
|
const workspace = await Workspace.findById(workspaceId).exec();
|
||||||
const workspace = await Workspace.findById(workspaceId).exec();
|
if (!workspace) {
|
||||||
if (!workspace) {
|
throw new Error('Failed to create workspace environment');
|
||||||
throw new Error('Failed to create workspace environment');
|
|
||||||
}
|
|
||||||
|
|
||||||
const envIndex = workspace?.environments.findIndex(
|
|
||||||
({ slug }) => slug === environmentSlug
|
|
||||||
);
|
|
||||||
if (envIndex === -1) {
|
|
||||||
throw new Error('Invalid environment given');
|
|
||||||
}
|
|
||||||
|
|
||||||
workspace.environments.splice(envIndex, 1);
|
|
||||||
await workspace.save();
|
|
||||||
|
|
||||||
// clean up
|
|
||||||
await Secret.deleteMany({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment: environmentSlug,
|
|
||||||
});
|
|
||||||
await SecretVersion.deleteMany({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment: environmentSlug,
|
|
||||||
});
|
|
||||||
await ServiceToken.deleteMany({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment: environmentSlug,
|
|
||||||
});
|
|
||||||
await ServiceTokenData.deleteMany({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment: environmentSlug,
|
|
||||||
});
|
|
||||||
await Integration.deleteMany({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment: environmentSlug,
|
|
||||||
});
|
|
||||||
await Membership.updateMany(
|
|
||||||
{ workspace: workspaceId },
|
|
||||||
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
|
|
||||||
)
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete workspace environment',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const envIndex = workspace?.environments.findIndex(
|
||||||
|
({ slug }) => slug === environmentSlug
|
||||||
|
);
|
||||||
|
if (envIndex === -1) {
|
||||||
|
throw new Error('Invalid environment given');
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.environments.splice(envIndex, 1);
|
||||||
|
await workspace.save();
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
await Secret.deleteMany({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
});
|
||||||
|
await SecretVersion.deleteMany({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
});
|
||||||
|
await ServiceToken.deleteMany({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
});
|
||||||
|
await ServiceTokenData.deleteMany({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
});
|
||||||
|
await Integration.deleteMany({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment: environmentSlug,
|
||||||
|
});
|
||||||
|
await Membership.updateMany(
|
||||||
|
{ workspace: workspaceId },
|
||||||
|
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
|
||||||
|
)
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully deleted environment',
|
message: 'Successfully deleted environment',
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
@@ -49,20 +48,11 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let memberships;
|
const { organizationId } = req.params;
|
||||||
try {
|
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
memberships = await MembershipOrg.find({
|
const memberships = await MembershipOrg.find({
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
}).populate('user', '+publicKey');
|
}).populate('user', '+publicKey');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get organization memberships'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
memberships
|
memberships
|
||||||
@@ -128,26 +118,17 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let membership;
|
const { membershipId } = req.params;
|
||||||
try {
|
const { role } = req.body;
|
||||||
const { membershipId } = req.params;
|
|
||||||
const { role } = req.body;
|
const membership = await MembershipOrg.findByIdAndUpdate(
|
||||||
|
membershipId,
|
||||||
membership = await MembershipOrg.findByIdAndUpdate(
|
{
|
||||||
membershipId,
|
role
|
||||||
{
|
}, {
|
||||||
role
|
new: true
|
||||||
}, {
|
}
|
||||||
new: true
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to update organization membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership
|
membership
|
||||||
@@ -197,25 +178,16 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let membership;
|
const { membershipId } = req.params;
|
||||||
try {
|
|
||||||
const { membershipId } = req.params;
|
// delete organization membership
|
||||||
|
const membership = await deleteMembershipOrg({
|
||||||
// delete organization membership
|
membershipOrgId: membershipId
|
||||||
membership = await deleteMembershipOrg({
|
});
|
||||||
membershipOrgId: membershipId
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateSubscriptionOrgQuantity({
|
await updateSubscriptionOrgQuantity({
|
||||||
organizationId: membership.organization.toString()
|
organizationId: membership.organization.toString()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete organization membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership
|
membership
|
||||||
@@ -303,4 +275,4 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceAccounts
|
serviceAccounts
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
@@ -144,4 +143,4 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { User, MembershipOrg } from '../../models';
|
import { User, MembershipOrg } from '../../models';
|
||||||
import { completeAccount } from '../../helpers/user';
|
import { completeAccount } from '../../helpers/user';
|
||||||
import {
|
import {
|
||||||
@@ -20,136 +19,128 @@ import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
|||||||
*/
|
*/
|
||||||
export const completeAccountSignup = async (req: Request, res: Response) => {
|
export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||||
let user, token, refreshToken;
|
let user, token, refreshToken;
|
||||||
try {
|
const {
|
||||||
const {
|
email,
|
||||||
email,
|
firstName,
|
||||||
firstName,
|
lastName,
|
||||||
lastName,
|
protectedKey,
|
||||||
protectedKey,
|
protectedKeyIV,
|
||||||
protectedKeyIV,
|
protectedKeyTag,
|
||||||
protectedKeyTag,
|
publicKey,
|
||||||
publicKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyTag,
|
||||||
encryptedPrivateKeyTag,
|
salt,
|
||||||
salt,
|
verifier,
|
||||||
verifier,
|
organizationName
|
||||||
organizationName
|
}: {
|
||||||
}: {
|
email: string;
|
||||||
email: string;
|
firstName: string;
|
||||||
firstName: string;
|
lastName: string;
|
||||||
lastName: string;
|
protectedKey: string;
|
||||||
protectedKey: string;
|
protectedKeyIV: string;
|
||||||
protectedKeyIV: string;
|
protectedKeyTag: string;
|
||||||
protectedKeyTag: string;
|
publicKey: string;
|
||||||
publicKey: string;
|
encryptedPrivateKey: string;
|
||||||
encryptedPrivateKey: string;
|
encryptedPrivateKeyIV: string;
|
||||||
encryptedPrivateKeyIV: string;
|
encryptedPrivateKeyTag: string;
|
||||||
encryptedPrivateKeyTag: string;
|
salt: string;
|
||||||
salt: string;
|
verifier: string;
|
||||||
verifier: string;
|
organizationName: string;
|
||||||
organizationName: string;
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
// get user
|
// get user
|
||||||
user = await User.findOne({ email });
|
user = await User.findOne({ email });
|
||||||
|
|
||||||
if (!user || (user && user?.publicKey)) {
|
if (!user || (user && user?.publicKey)) {
|
||||||
// case 1: user doesn't exist.
|
// case 1: user doesn't exist.
|
||||||
// case 2: user has already completed account
|
// case 2: user has already completed account
|
||||||
return res.status(403).send({
|
return res.status(403).send({
|
||||||
error: 'Failed to complete account for complete user'
|
error: 'Failed to complete account for complete user'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete setting up user's account
|
// complete setting up user's account
|
||||||
user = await completeAccount({
|
user = await completeAccount({
|
||||||
userId: user._id.toString(),
|
userId: user._id.toString(),
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyTag,
|
encryptedPrivateKeyTag,
|
||||||
salt,
|
salt,
|
||||||
verifier
|
verifier
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null
|
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null
|
||||||
|
|
||||||
// initialize default organization and workspace
|
// initialize default organization and workspace
|
||||||
await initializeDefaultOrg({
|
await initializeDefaultOrg({
|
||||||
organizationName,
|
organizationName,
|
||||||
user
|
user
|
||||||
});
|
});
|
||||||
|
|
||||||
// update organization membership statuses that are
|
// update organization membership statuses that are
|
||||||
// invited to completed with user attached
|
// invited to completed with user attached
|
||||||
const membershipsToUpdate = await MembershipOrg.find({
|
const membershipsToUpdate = await MembershipOrg.find({
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
});
|
});
|
||||||
|
|
||||||
membershipsToUpdate.forEach(async (membership) => {
|
membershipsToUpdate.forEach(async (membership) => {
|
||||||
await updateSubscriptionOrgQuantity({
|
await updateSubscriptionOrgQuantity({
|
||||||
organizationId: membership.organization.toString()
|
organizationId: membership.organization.toString()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// update organization membership statuses that are
|
// update organization membership statuses that are
|
||||||
// invited to completed with user attached
|
// invited to completed with user attached
|
||||||
await MembershipOrg.updateMany(
|
await MembershipOrg.updateMany(
|
||||||
{
|
{
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
user,
|
user,
|
||||||
status: ACCEPTED
|
status: ACCEPTED
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({
|
const tokens = await issueAuthTokens({
|
||||||
userId: user._id.toString()
|
userId: user._id.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
token = tokens.token;
|
||||||
|
|
||||||
// sending a welcome email to new users
|
// sending a welcome email to new users
|
||||||
if (await getLoopsApiKey()) {
|
if (await getLoopsApiKey()) {
|
||||||
await standardRequest.post("https://app.loops.so/api/v1/events/send", {
|
await standardRequest.post("https://app.loops.so/api/v1/events/send", {
|
||||||
"email": email,
|
"email": email,
|
||||||
"eventName": "Sign Up",
|
"eventName": "Sign Up",
|
||||||
"firstName": firstName,
|
"firstName": firstName,
|
||||||
"lastName": lastName
|
"lastName": lastName
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Authorization": "Bearer " + (await getLoopsApiKey())
|
"Authorization": "Bearer " + (await getLoopsApiKey())
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: await getHttpsEnabled()
|
secure: await getHttpsEnabled()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to complete account setup'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully set up account',
|
message: 'Successfully set up account',
|
||||||
@@ -167,109 +158,101 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const completeAccountInvite = async (req: Request, res: Response) => {
|
export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||||
let user, token, refreshToken;
|
let user, token, refreshToken;
|
||||||
try {
|
const {
|
||||||
const {
|
email,
|
||||||
email,
|
firstName,
|
||||||
firstName,
|
lastName,
|
||||||
lastName,
|
protectedKey,
|
||||||
protectedKey,
|
protectedKeyIV,
|
||||||
protectedKeyIV,
|
protectedKeyTag,
|
||||||
protectedKeyTag,
|
publicKey,
|
||||||
publicKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyTag,
|
||||||
encryptedPrivateKeyTag,
|
salt,
|
||||||
salt,
|
verifier
|
||||||
verifier
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
// get user
|
// get user
|
||||||
user = await User.findOne({ email });
|
user = await User.findOne({ email });
|
||||||
|
|
||||||
if (!user || (user && user?.publicKey)) {
|
if (!user || (user && user?.publicKey)) {
|
||||||
// case 1: user doesn't exist.
|
// case 1: user doesn't exist.
|
||||||
// case 2: user has already completed account
|
// case 2: user has already completed account
|
||||||
return res.status(403).send({
|
return res.status(403).send({
|
||||||
error: 'Failed to complete account for complete user'
|
error: 'Failed to complete account for complete user'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg) throw new Error('Failed to find invitations for email');
|
if (!membershipOrg) throw new Error('Failed to find invitations for email');
|
||||||
|
|
||||||
// complete setting up user's account
|
// complete setting up user's account
|
||||||
user = await completeAccount({
|
user = await completeAccount({
|
||||||
userId: user._id.toString(),
|
userId: user._id.toString(),
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyTag,
|
encryptedPrivateKeyTag,
|
||||||
salt,
|
salt,
|
||||||
verifier
|
verifier
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
throw new Error('Failed to complete account for non-existent user');
|
throw new Error('Failed to complete account for non-existent user');
|
||||||
|
|
||||||
// update organization membership statuses that are
|
// update organization membership statuses that are
|
||||||
// invited to completed with user attached
|
// invited to completed with user attached
|
||||||
const membershipsToUpdate = await MembershipOrg.find({
|
const membershipsToUpdate = await MembershipOrg.find({
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
});
|
});
|
||||||
|
|
||||||
membershipsToUpdate.forEach(async (membership) => {
|
membershipsToUpdate.forEach(async (membership) => {
|
||||||
await updateSubscriptionOrgQuantity({
|
await updateSubscriptionOrgQuantity({
|
||||||
organizationId: membership.organization.toString()
|
organizationId: membership.organization.toString()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await MembershipOrg.updateMany(
|
await MembershipOrg.updateMany(
|
||||||
{
|
{
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
status: INVITED
|
status: INVITED
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
user,
|
user,
|
||||||
status: ACCEPTED
|
status: ACCEPTED
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({
|
const tokens = await issueAuthTokens({
|
||||||
userId: user._id.toString()
|
userId: user._id.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
token = tokens.token;
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: await getHttpsEnabled()
|
secure: await getHttpsEnabled()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to complete account setup'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully set up account',
|
message: 'Successfully set up account',
|
||||||
user,
|
user,
|
||||||
token
|
token
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Membership, Secret,
|
Membership, Secret,
|
||||||
@@ -69,4 +68,4 @@ export const getWorkspaceTags = async (req: Request, res: Response) => {
|
|||||||
return res.json({
|
return res.json({
|
||||||
workspaceTags
|
workspaceTags
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
import {
|
||||||
User,
|
User,
|
||||||
MembershipOrg
|
MembershipOrg
|
||||||
@@ -37,18 +36,9 @@ export const getMe = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let user;
|
const user = await User
|
||||||
try {
|
.findById(req.user._id)
|
||||||
user = await User
|
.select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag');
|
||||||
.findById(req.user._id)
|
|
||||||
.select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag');
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get current user'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
user
|
user
|
||||||
@@ -64,29 +54,20 @@ export const getMe = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateMyMfaEnabled = async (req: Request, res: Response) => {
|
export const updateMyMfaEnabled = async (req: Request, res: Response) => {
|
||||||
let user;
|
const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body;
|
||||||
try {
|
req.user.isMfaEnabled = isMfaEnabled;
|
||||||
const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body;
|
|
||||||
req.user.isMfaEnabled = isMfaEnabled;
|
if (isMfaEnabled) {
|
||||||
|
// TODO: adapt this route/controller
|
||||||
if (isMfaEnabled) {
|
// to work for different forms of MFA
|
||||||
// TODO: adapt this route/controller
|
req.user.mfaMethods = ['email'];
|
||||||
// to work for different forms of MFA
|
} else {
|
||||||
req.user.mfaMethods = ['email'];
|
req.user.mfaMethods = [];
|
||||||
} else {
|
|
||||||
req.user.mfaMethods = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
await req.user.save();
|
|
||||||
|
|
||||||
user = req.user;
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to update current user's MFA status"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await req.user.save();
|
||||||
|
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
user
|
user
|
||||||
@@ -126,22 +107,13 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let organizations;
|
const organizations = (
|
||||||
try {
|
await MembershipOrg.find({
|
||||||
organizations = (
|
user: req.user._id
|
||||||
await MembershipOrg.find({
|
}).populate('organization')
|
||||||
user: req.user._id
|
).map((m) => m.organization);
|
||||||
}).populate('organization')
|
|
||||||
).map((m) => m.organization);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get current user's organizations"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organizations
|
organizations
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Workspace,
|
Workspace,
|
||||||
@@ -47,66 +46,57 @@ interface V2PushSecret {
|
|||||||
*/
|
*/
|
||||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
try {
|
const postHogClient = await TelemetryService.getPostHogClient();
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
const { keys, environment, channel } = req.body;
|
||||||
const { keys, environment, channel } = req.body;
|
const { workspaceId } = req.params;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
// validate environment
|
// validate environment
|
||||||
const workspaceEnvs = req.membership.workspace.environments;
|
const workspaceEnvs = req.membership.workspace.environments;
|
||||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||||
throw new Error('Failed to validate environment');
|
throw new Error('Failed to validate environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitize secrets
|
// sanitize secrets
|
||||||
secrets = secrets.filter(
|
secrets = secrets.filter(
|
||||||
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
|
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
|
||||||
);
|
);
|
||||||
|
|
||||||
await push({
|
await push({
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secrets,
|
secrets,
|
||||||
channel: channel ? channel : 'cli',
|
channel: channel ? channel : 'cli',
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
await pushKeys({
|
await pushKeys({
|
||||||
userId: req.user._id,
|
userId: req.user._id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
keys
|
keys
|
||||||
});
|
});
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets pushed',
|
event: 'secrets pushed',
|
||||||
distinctId: req.user.email,
|
distinctId: req.user.email,
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel ? channel : 'cli'
|
channel: channel ? channel : 'cli'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
EventService.handleEvent({
|
EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
environment
|
environment
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to upload workspace secrets'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully uploaded workspace secrets'
|
message: 'Successfully uploaded workspace secrets'
|
||||||
@@ -121,57 +111,49 @@ 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();
|
||||||
try {
|
const environment: string = req.query.environment as string;
|
||||||
const postHogClient = await TelemetryService.getPostHogClient();
|
const channel: string = req.query.channel as string;
|
||||||
const environment: string = req.query.environment as string;
|
const { workspaceId } = req.params;
|
||||||
const channel: string = req.query.channel as string;
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
let userId;
|
let userId;
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
userId = req.user._id.toString();
|
userId = req.user._id.toString();
|
||||||
} else if (req.serviceTokenData) {
|
} else if (req.serviceTokenData) {
|
||||||
userId = req.serviceTokenData.user.toString();
|
userId = req.serviceTokenData.user.toString();
|
||||||
}
|
}
|
||||||
// validate environment
|
// validate environment
|
||||||
const workspaceEnvs = req.membership.workspace.environments;
|
const workspaceEnvs = req.membership.workspace.environments;
|
||||||
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
|
||||||
throw new Error('Failed to validate environment');
|
throw new Error('Failed to validate environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets = await pull({
|
let secrets = await pull({
|
||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
channel: channel ? channel : 'cli',
|
channel: channel ? channel : 'cli',
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
if (channel !== 'cli') {
|
if (channel !== 'cli') {
|
||||||
secrets = reformatPullSecrets({ secrets });
|
// FIX: Fix this any
|
||||||
}
|
secrets = reformatPullSecrets({ secrets }) as any;
|
||||||
|
}
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
// capture secrets pushed event in production
|
// capture secrets pushed event in production
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
distinctId: req.user.email,
|
distinctId: req.user.email,
|
||||||
event: 'secrets pulled',
|
event: 'secrets pulled',
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
channel: channel ? channel : 'cli'
|
channel: channel ? channel : 'cli'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to pull workspace secrets'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets
|
secrets
|
||||||
@@ -208,22 +190,14 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let key;
|
let key;
|
||||||
try {
|
const { workspaceId } = req.params;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
key = await Key.findOne({
|
key = await Key.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
receiver: req.user._id
|
receiver: req.user._id
|
||||||
}).populate('sender', '+publicKey');
|
}).populate('sender', '+publicKey');
|
||||||
|
|
||||||
if (!key) throw new Error('Failed to find workspace key');
|
if (!key) throw new Error('Failed to find workspace key');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get workspace key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(key);
|
return res.status(200).json(key);
|
||||||
}
|
}
|
||||||
@@ -231,23 +205,13 @@ export const getWorkspaceServiceTokenData = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let serviceTokenData;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenData
|
const serviceTokenData = await ServiceTokenData
|
||||||
.find({
|
.find({
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
})
|
})
|
||||||
.select('+encryptedKey +iv +tag');
|
.select('+encryptedKey +iv +tag');
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get workspace service token data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
@@ -294,20 +258,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let memberships;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
memberships = await Membership.find({
|
const memberships = await Membership.find({
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
}).populate('user', '+publicKey');
|
}).populate('user', '+publicKey');
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to get workspace memberships'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
memberships
|
memberships
|
||||||
@@ -374,29 +329,20 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let membership;
|
const {
|
||||||
try {
|
membershipId
|
||||||
const {
|
} = req.params;
|
||||||
membershipId
|
const { role } = req.body;
|
||||||
} = req.params;
|
|
||||||
const { role } = req.body;
|
const membership = await Membership.findByIdAndUpdate(
|
||||||
|
membershipId,
|
||||||
membership = await Membership.findByIdAndUpdate(
|
{
|
||||||
membershipId,
|
role
|
||||||
{
|
}, {
|
||||||
role
|
new: true
|
||||||
}, {
|
}
|
||||||
new: true
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to update workspace membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership
|
membership
|
||||||
});
|
});
|
||||||
@@ -445,27 +391,18 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let membership;
|
const {
|
||||||
try {
|
membershipId
|
||||||
const {
|
} = req.params;
|
||||||
membershipId
|
|
||||||
} = req.params;
|
const membership = await Membership.findByIdAndDelete(membershipId);
|
||||||
|
|
||||||
membership = await Membership.findByIdAndDelete(membershipId);
|
if (!membership) throw new Error('Failed to delete workspace membership');
|
||||||
|
|
||||||
if (!membership) throw new Error('Failed to delete workspace membership');
|
await Key.deleteMany({
|
||||||
|
receiver: membership.user,
|
||||||
await Key.deleteMany({
|
workspace: membership.workspace
|
||||||
receiver: membership.user,
|
});
|
||||||
workspace: membership.workspace
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete workspace membership'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membership
|
membership
|
||||||
@@ -479,32 +416,23 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
||||||
let workspace;
|
const { workspaceId } = req.params;
|
||||||
try {
|
const { autoCapitalization } = req.body;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
const { autoCapitalization } = req.body;
|
|
||||||
|
|
||||||
workspace = await Workspace.findOneAndUpdate(
|
const workspace = await Workspace.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: workspaceId
|
_id: workspaceId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
autoCapitalization
|
autoCapitalization
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to change autoCapitalization setting'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully changed autoCapitalization setting',
|
message: 'Successfully changed autoCapitalization setting',
|
||||||
workspace
|
workspace
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Action, SecretVersion } from '../../models';
|
import { Action, SecretVersion } from '../../models';
|
||||||
import { ActionNotFoundError } from '../../../utils/errors';
|
import { ActionNotFoundError } from '../../../utils/errors';
|
||||||
|
|
||||||
@@ -28,4 +27,4 @@ export const getAction = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
action
|
action
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { EELicenseService } from '../../services';
|
import { EELicenseService } from '../../services';
|
||||||
import { getLicenseServerUrl } from '../../../config';
|
import { getLicenseServerUrl } from '../../../config';
|
||||||
@@ -12,23 +11,18 @@ import { licenseServerKeyRequest } from '../../../config/request';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getCloudProducts = async (req: Request, res: Response) => {
|
export const getCloudProducts = async (req: Request, res: Response) => {
|
||||||
try {
|
const billingCycle = req.query['billing-cycle'] as string;
|
||||||
const billingCycle = req.query['billing-cycle'] as string;
|
|
||||||
|
|
||||||
if (EELicenseService.instanceType === 'cloud') {
|
if (EELicenseService.instanceType === 'cloud') {
|
||||||
const { data } = await licenseServerKeyRequest.get(
|
const { data } = await licenseServerKeyRequest.get(
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send(data);
|
return res.status(200).send(data);
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
head: [],
|
head: [],
|
||||||
rows: []
|
rows: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import * as Sentry from "@sentry/node";
|
|
||||||
import { Secret } from "../../../models";
|
import { Secret } from "../../../models";
|
||||||
import { SecretVersion } from "../../models";
|
import { SecretVersion } from "../../models";
|
||||||
import { EESecretService } from "../../services";
|
import { EESecretService } from "../../services";
|
||||||
@@ -55,29 +54,20 @@ export const getSecretVersions = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let secretVersions;
|
const { secretId, workspaceId, environment, folderId } = req.params;
|
||||||
try {
|
|
||||||
const { secretId, workspaceId, environment, folderId } = req.params;
|
|
||||||
|
|
||||||
const offset: number = parseInt(req.query.offset as string);
|
const offset: number = parseInt(req.query.offset as string);
|
||||||
const limit: number = parseInt(req.query.limit as string);
|
const limit: number = parseInt(req.query.limit as string);
|
||||||
|
|
||||||
secretVersions = await SecretVersion.find({
|
const secretVersions = await SecretVersion.find({
|
||||||
secret: secretId,
|
secret: secretId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
})
|
})
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get secret versions",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secretVersions,
|
secretVersions,
|
||||||
@@ -139,74 +129,45 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let secret;
|
const { secretId } = req.params;
|
||||||
try {
|
const { version } = req.body;
|
||||||
const { secretId } = req.params;
|
|
||||||
const { version } = req.body;
|
|
||||||
|
|
||||||
// validate secret version
|
// validate secret version
|
||||||
const oldSecretVersion = await SecretVersion.findOne({
|
const oldSecretVersion = await SecretVersion.findOne({
|
||||||
secret: secretId,
|
secret: secretId,
|
||||||
version,
|
version,
|
||||||
}).select("+secretBlindIndex");
|
}).select("+secretBlindIndex");
|
||||||
|
|
||||||
if (!oldSecretVersion) throw new Error("Failed to find secret version");
|
if (!oldSecretVersion) throw new Error("Failed to find secret version");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
workspace,
|
workspace,
|
||||||
type,
|
type,
|
||||||
user,
|
user,
|
||||||
environment,
|
environment,
|
||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag,
|
secretKeyTag,
|
||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
folder,
|
algorithm,
|
||||||
algorithm,
|
folder,
|
||||||
keyEncoding,
|
keyEncoding,
|
||||||
} = oldSecretVersion;
|
} = oldSecretVersion;
|
||||||
|
|
||||||
// update secret
|
// update secret
|
||||||
secret = await Secret.findByIdAndUpdate(
|
const secret = await Secret.findByIdAndUpdate(
|
||||||
secretId,
|
secretId,
|
||||||
{
|
{
|
||||||
$inc: {
|
$inc: {
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
|
||||||
workspace,
|
|
||||||
type,
|
|
||||||
user,
|
|
||||||
environment,
|
|
||||||
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
folderId: folder,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding,
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
new: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!secret) throw new Error("Failed to find and update secret");
|
|
||||||
|
|
||||||
// add new secret version
|
|
||||||
await new SecretVersion({
|
|
||||||
secret: secretId,
|
|
||||||
version: secret.version,
|
|
||||||
workspace,
|
workspace,
|
||||||
type,
|
type,
|
||||||
user,
|
user,
|
||||||
environment,
|
environment,
|
||||||
isDeleted: false,
|
|
||||||
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
@@ -214,24 +175,44 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
|||||||
secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag,
|
secretValueTag,
|
||||||
folder,
|
folderId: folder,
|
||||||
algorithm,
|
algorithm,
|
||||||
keyEncoding,
|
keyEncoding,
|
||||||
}).save();
|
},
|
||||||
|
{
|
||||||
|
new: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// take secret snapshot
|
if (!secret) throw new Error("Failed to find and update secret");
|
||||||
await EESecretService.takeSecretSnapshot({
|
|
||||||
workspaceId: secret.workspace,
|
// add new secret version
|
||||||
environment,
|
await new SecretVersion({
|
||||||
folderId: folder,
|
secret: secretId,
|
||||||
});
|
version: secret.version,
|
||||||
} catch (err) {
|
workspace,
|
||||||
Sentry.setUser({ email: req.user.email });
|
type,
|
||||||
Sentry.captureException(err);
|
user,
|
||||||
return res.status(400).send({
|
environment,
|
||||||
message: "Failed to roll back secret version",
|
isDeleted: false,
|
||||||
});
|
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
||||||
}
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
folder,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding,
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
// take secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment,
|
||||||
|
folderId: folder,
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secret,
|
secret,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import * as Sentry from "@sentry/node";
|
|
||||||
import {
|
import {
|
||||||
ISecretVersion,
|
ISecretVersion,
|
||||||
SecretSnapshot,
|
SecretSnapshot,
|
||||||
@@ -13,23 +12,14 @@ import {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSecretSnapshot = async (req: Request, res: Response) => {
|
export const getSecretSnapshot = async (req: Request, res: Response) => {
|
||||||
let secretSnapshot;
|
const { secretSnapshotId } = req.params;
|
||||||
try {
|
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId)
|
||||||
const { secretSnapshotId } = req.params;
|
.lean()
|
||||||
|
.populate<{ secretVersions: ISecretVersion[] }>("secretVersions")
|
||||||
|
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
|
||||||
|
|
||||||
secretSnapshot = await SecretSnapshot.findById(secretSnapshotId)
|
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
|
||||||
.lean()
|
|
||||||
.populate<{ secretVersions: ISecretVersion[] }>("secretVersions")
|
|
||||||
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
|
|
||||||
|
|
||||||
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get secret snapshot",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const folderId = secretSnapshot.folderId;
|
const folderId = secretSnapshot.folderId;
|
||||||
// to show only the folder required secrets
|
// to show only the folder required secrets
|
||||||
secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter(
|
secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
|
import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
|
||||||
@@ -10,26 +9,17 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const handleWebhook = async (req: Request, res: Response) => {
|
export const handleWebhook = async (req: Request, res: Response) => {
|
||||||
let event;
|
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||||
try {
|
apiVersion: '2022-08-01'
|
||||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
});
|
||||||
apiVersion: '2022-08-01'
|
|
||||||
});
|
|
||||||
|
|
||||||
// check request for valid stripe signature
|
// check request for valid stripe signature
|
||||||
const sig = req.headers['stripe-signature'] as string;
|
const sig = req.headers['stripe-signature'] as string;
|
||||||
event = stripe.webhooks.constructEvent(
|
const event = stripe.webhooks.constructEvent(
|
||||||
req.body,
|
req.body,
|
||||||
sig,
|
sig,
|
||||||
await getStripeWebhookSecret()
|
await getStripeWebhookSecret()
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Failed to process webhook'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case '':
|
case '':
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import * as Sentry from "@sentry/node";
|
|
||||||
import { PipelineStage, Types } from "mongoose";
|
import { PipelineStage, Types } from "mongoose";
|
||||||
import { Secret } from "../../../models";
|
import { Secret } from "../../../models";
|
||||||
import {
|
import {
|
||||||
@@ -69,29 +68,20 @@ export const getWorkspaceSecretSnapshots = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let secretSnapshots;
|
const { workspaceId } = req.params;
|
||||||
try {
|
const { environment, folderId } = req.query;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
const { environment, folderId } = req.query;
|
|
||||||
|
|
||||||
const offset: number = parseInt(req.query.offset as string);
|
const offset: number = parseInt(req.query.offset as string);
|
||||||
const limit: number = parseInt(req.query.limit as string);
|
const limit: number = parseInt(req.query.limit as string);
|
||||||
|
|
||||||
secretSnapshots = await SecretSnapshot.find({
|
const secretSnapshots = await SecretSnapshot.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId: folderId || "root",
|
folderId: folderId || "root",
|
||||||
})
|
})
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.limit(limit);
|
.limit(limit);
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get secret snapshots",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secretSnapshots,
|
secretSnapshots,
|
||||||
@@ -107,23 +97,14 @@ export const getWorkspaceSecretSnapshotsCount = async (
|
|||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
let count;
|
const { workspaceId } = req.params;
|
||||||
try {
|
const { environment, folderId } = req.query;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
const { environment, folderId } = req.query;
|
|
||||||
|
|
||||||
count = await SecretSnapshot.countDocuments({
|
const count = await SecretSnapshot.countDocuments({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId: folderId || "root",
|
folderId: folderId || "root",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to count number of secret snapshots",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
count,
|
count,
|
||||||
@@ -191,324 +172,315 @@ export const rollbackWorkspaceSecretSnapshot = async (
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let secrets;
|
const { workspaceId } = req.params;
|
||||||
try {
|
const { version, environment, folderId = "root" } = req.body;
|
||||||
const { workspaceId } = req.params;
|
|
||||||
const { version, environment, folderId = "root" } = req.body;
|
|
||||||
|
|
||||||
// validate secret snapshot
|
// validate secret snapshot
|
||||||
const secretSnapshot = await SecretSnapshot.findOne({
|
const secretSnapshot = await SecretSnapshot.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
version,
|
version,
|
||||||
environment,
|
environment,
|
||||||
folderId: folderId,
|
folderId: folderId,
|
||||||
|
})
|
||||||
|
.populate<{ secretVersions: ISecretVersion[] }>({
|
||||||
|
path: "secretVersions",
|
||||||
|
select: "+secretBlindIndex",
|
||||||
})
|
})
|
||||||
.populate<{ secretVersions: ISecretVersion[] }>({
|
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
|
||||||
path: "secretVersions",
|
|
||||||
select: "+secretBlindIndex",
|
|
||||||
})
|
|
||||||
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
|
|
||||||
|
|
||||||
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
|
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
|
||||||
|
|
||||||
const snapshotFolderTree = secretSnapshot.folderVersion;
|
const snapshotFolderTree = secretSnapshot.folderVersion;
|
||||||
const latestFolderTree = await Folder.findOne({
|
const latestFolderTree = await Folder.findOne({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
});
|
});
|
||||||
|
|
||||||
const latestFolderVersion = await FolderVersion.findOne({
|
const latestFolderVersion = await FolderVersion.findOne({
|
||||||
environment,
|
environment,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
"nodes.id": folderId,
|
"nodes.id": folderId,
|
||||||
}).sort({ "nodes.version": -1 });
|
}).sort({ "nodes.version": -1 });
|
||||||
|
|
||||||
const oldSecretVersionsObj: Record<string, ISecretVersion> = {};
|
const oldSecretVersionsObj: Record<string, ISecretVersion> = {};
|
||||||
const secretIds: Types.ObjectId[] = [];
|
const secretIds: Types.ObjectId[] = [];
|
||||||
const folderIds: string[] = [folderId];
|
const folderIds: string[] = [folderId];
|
||||||
|
|
||||||
secretSnapshot.secretVersions.forEach((snapSecVer) => {
|
secretSnapshot.secretVersions.forEach((snapSecVer) => {
|
||||||
oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer;
|
oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer;
|
||||||
secretIds.push(snapSecVer.secret);
|
secretIds.push(snapSecVer.secret);
|
||||||
});
|
});
|
||||||
|
|
||||||
// the parent node from current latest one
|
// the parent node from current latest one
|
||||||
// this will be modified according to the snapshot and latest snapshots
|
// this will be modified according to the snapshot and latest snapshots
|
||||||
const newFolderTree =
|
const newFolderTree =
|
||||||
latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
|
latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
|
||||||
|
|
||||||
if (newFolderTree) {
|
if (newFolderTree) {
|
||||||
newFolderTree.children = snapshotFolderTree?.nodes?.children || [];
|
newFolderTree.children = snapshotFolderTree?.nodes?.children || [];
|
||||||
const queue = [newFolderTree];
|
const queue = [newFolderTree];
|
||||||
// a bfs algorithm in which we take the latest snapshots of all the folders in a level
|
// a bfs algorithm in which we take the latest snapshots of all the folders in a level
|
||||||
|
while (queue.length) {
|
||||||
|
const groupByFolderId: Record<string, TFolderSchema> = {};
|
||||||
|
// the original queue is popped out completely to get what ever in a level
|
||||||
|
// subqueue is filled with all the children thus next level folders
|
||||||
|
// subQueue will then be transfered to the oriinal queue
|
||||||
|
const subQueue: TFolderSchema[] = [];
|
||||||
|
// get everything inside a level
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const groupByFolderId: Record<string, TFolderSchema> = {};
|
const folder = queue.pop() as TFolderSchema;
|
||||||
// the original queue is popped out completely to get what ever in a level
|
folder.children.forEach((el) => {
|
||||||
// subqueue is filled with all the children thus next level folders
|
folderIds.push(el.id); // push ids and data into queu
|
||||||
// subQueue will then be transfered to the oriinal queue
|
subQueue.push(el);
|
||||||
const subQueue: TFolderSchema[] = [];
|
// to modify the original tree very fast we keep a reference object
|
||||||
// get everything inside a level
|
// key with folder id and pointing to the various nodes
|
||||||
while (queue.length) {
|
groupByFolderId[el.id] = el;
|
||||||
const folder = queue.pop() as TFolderSchema;
|
|
||||||
folder.children.forEach((el) => {
|
|
||||||
folderIds.push(el.id); // push ids and data into queu
|
|
||||||
subQueue.push(el);
|
|
||||||
// to modify the original tree very fast we keep a reference object
|
|
||||||
// key with folder id and pointing to the various nodes
|
|
||||||
groupByFolderId[el.id] = el;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// get latest snapshots of all the folder
|
|
||||||
const matchWsFoldersPipeline = {
|
|
||||||
$match: {
|
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
folderId: {
|
|
||||||
$in: Object.keys(groupByFolderId),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const sortByFolderIdAndVersion: PipelineStage = {
|
|
||||||
$sort: { folderId: 1, version: -1 },
|
|
||||||
};
|
|
||||||
const pickLatestVersionOfEachFolder = {
|
|
||||||
$group: {
|
|
||||||
_id: "$folderId",
|
|
||||||
latestVersion: { $first: "$version" },
|
|
||||||
doc: {
|
|
||||||
$first: "$$ROOT",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const populateSecVersion = {
|
|
||||||
$lookup: {
|
|
||||||
from: SecretVersion.collection.name,
|
|
||||||
localField: "doc.secretVersions",
|
|
||||||
foreignField: "_id",
|
|
||||||
as: "doc.secretVersions",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const populateFolderVersion = {
|
|
||||||
$lookup: {
|
|
||||||
from: FolderVersion.collection.name,
|
|
||||||
localField: "doc.folderVersion",
|
|
||||||
foreignField: "_id",
|
|
||||||
as: "doc.folderVersion",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const unwindFolderVerField = {
|
|
||||||
$unwind: {
|
|
||||||
path: "$doc.folderVersion",
|
|
||||||
preserveNullAndEmptyArrays: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> =
|
|
||||||
await SecretSnapshot.aggregate([
|
|
||||||
matchWsFoldersPipeline,
|
|
||||||
sortByFolderIdAndVersion,
|
|
||||||
pickLatestVersionOfEachFolder,
|
|
||||||
populateSecVersion,
|
|
||||||
populateFolderVersion,
|
|
||||||
unwindFolderVerField,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// recursive snapshotting each level
|
|
||||||
latestSnapshotsByFolders.forEach((snap) => {
|
|
||||||
// mutate the folder tree to update the nodes to the latest version tree
|
|
||||||
// we are reconstructing the folder tree by latest snapshots here
|
|
||||||
if (groupByFolderId[snap.doc.folderId]) {
|
|
||||||
groupByFolderId[snap.doc.folderId].children =
|
|
||||||
snap.doc?.folderVersion?.nodes?.children || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// push all children of next level snapshots
|
|
||||||
if (snap.doc.folderVersion?.nodes?.children) {
|
|
||||||
queue.push(...snap.doc.folderVersion.nodes.children);
|
|
||||||
}
|
|
||||||
|
|
||||||
snap.doc.secretVersions.forEach((snapSecVer) => {
|
|
||||||
// record all the secrets
|
|
||||||
oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer;
|
|
||||||
secretIds.push(snapSecVer.secret);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
queue.push(...subQueue);
|
|
||||||
}
|
}
|
||||||
}
|
// get latest snapshots of all the folder
|
||||||
|
const matchWsFoldersPipeline = {
|
||||||
// TODO: fix any
|
$match: {
|
||||||
const latestSecretVersionIds = await getLatestSecretVersionIds({
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
secretIds,
|
environment,
|
||||||
});
|
folderId: {
|
||||||
|
$in: Object.keys(groupByFolderId),
|
||||||
// TODO: fix any
|
|
||||||
const latestSecretVersions: any = (
|
|
||||||
await SecretVersion.find(
|
|
||||||
{
|
|
||||||
_id: {
|
|
||||||
$in: latestSecretVersionIds.map((s) => s.versionId),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"secret version"
|
};
|
||||||
)
|
const sortByFolderIdAndVersion: PipelineStage = {
|
||||||
).reduce(
|
$sort: { folderId: 1, version: -1 },
|
||||||
(accumulator, s) => ({
|
};
|
||||||
...accumulator,
|
const pickLatestVersionOfEachFolder = {
|
||||||
[`${s.secret.toString()}`]: s,
|
$group: {
|
||||||
}),
|
_id: "$folderId",
|
||||||
{}
|
latestVersion: { $first: "$version" },
|
||||||
);
|
doc: {
|
||||||
|
$first: "$$ROOT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const populateSecVersion = {
|
||||||
|
$lookup: {
|
||||||
|
from: SecretVersion.collection.name,
|
||||||
|
localField: "doc.secretVersions",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "doc.secretVersions",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const populateFolderVersion = {
|
||||||
|
$lookup: {
|
||||||
|
from: FolderVersion.collection.name,
|
||||||
|
localField: "doc.folderVersion",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "doc.folderVersion",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const unwindFolderVerField = {
|
||||||
|
$unwind: {
|
||||||
|
path: "$doc.folderVersion",
|
||||||
|
preserveNullAndEmptyArrays: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> =
|
||||||
|
await SecretSnapshot.aggregate([
|
||||||
|
matchWsFoldersPipeline,
|
||||||
|
sortByFolderIdAndVersion,
|
||||||
|
pickLatestVersionOfEachFolder,
|
||||||
|
populateSecVersion,
|
||||||
|
populateFolderVersion,
|
||||||
|
unwindFolderVerField,
|
||||||
|
]);
|
||||||
|
|
||||||
const secDelQuery: Record<string, unknown> = {
|
// recursive snapshotting each level
|
||||||
workspace: workspaceId,
|
latestSnapshotsByFolders.forEach((snap) => {
|
||||||
environment,
|
// mutate the folder tree to update the nodes to the latest version tree
|
||||||
// undefined means root thus collect all secrets
|
// we are reconstructing the folder tree by latest snapshots here
|
||||||
};
|
if (groupByFolderId[snap.doc.folderId]) {
|
||||||
if (folderId !== "root" && folderIds.length)
|
groupByFolderId[snap.doc.folderId].children =
|
||||||
secDelQuery.folder = { $in: folderIds };
|
snap.doc?.folderVersion?.nodes?.children || [];
|
||||||
|
}
|
||||||
|
|
||||||
// delete existing secrets
|
// push all children of next level snapshots
|
||||||
await Secret.deleteMany(secDelQuery);
|
if (snap.doc.folderVersion?.nodes?.children) {
|
||||||
await Folder.deleteOne({
|
queue.push(...snap.doc.folderVersion.nodes.children);
|
||||||
workspace: workspaceId,
|
}
|
||||||
environment,
|
|
||||||
});
|
|
||||||
|
|
||||||
// add secrets
|
snap.doc.secretVersions.forEach((snapSecVer) => {
|
||||||
secrets = await Secret.insertMany(
|
// record all the secrets
|
||||||
Object.keys(oldSecretVersionsObj).map((sv) => {
|
oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer;
|
||||||
const {
|
secretIds.push(snapSecVer.secret);
|
||||||
secret: secretId,
|
});
|
||||||
workspace,
|
|
||||||
type,
|
|
||||||
user,
|
|
||||||
environment,
|
|
||||||
secretBlindIndex,
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
createdAt,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding,
|
|
||||||
folder: secFolderId,
|
|
||||||
} = oldSecretVersionsObj[sv];
|
|
||||||
|
|
||||||
return {
|
|
||||||
_id: secretId,
|
|
||||||
version: latestSecretVersions[secretId.toString()].version + 1,
|
|
||||||
workspace,
|
|
||||||
type,
|
|
||||||
user,
|
|
||||||
environment,
|
|
||||||
secretBlindIndex: secretBlindIndex ?? undefined,
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
secretCommentCiphertext: "",
|
|
||||||
secretCommentIV: "",
|
|
||||||
secretCommentTag: "",
|
|
||||||
createdAt,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding,
|
|
||||||
folder: secFolderId,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// add secret versions
|
|
||||||
const secretV = await SecretVersion.insertMany(
|
|
||||||
secrets.map(
|
|
||||||
({
|
|
||||||
_id,
|
|
||||||
version,
|
|
||||||
workspace,
|
|
||||||
type,
|
|
||||||
user,
|
|
||||||
environment,
|
|
||||||
secretBlindIndex,
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding,
|
|
||||||
folder: secFolderId,
|
|
||||||
}) => ({
|
|
||||||
_id: new Types.ObjectId(),
|
|
||||||
secret: _id,
|
|
||||||
version,
|
|
||||||
workspace,
|
|
||||||
type,
|
|
||||||
user,
|
|
||||||
environment,
|
|
||||||
isDeleted: false,
|
|
||||||
secretBlindIndex: secretBlindIndex ?? undefined,
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding,
|
|
||||||
folder: secFolderId,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newFolderTree && latestFolderTree) {
|
|
||||||
// save the updated folder tree to the present one
|
|
||||||
newFolderTree.version = (latestFolderVersion?.nodes?.version || 0) + 1;
|
|
||||||
latestFolderTree._id = new Types.ObjectId();
|
|
||||||
latestFolderTree.isNew = true;
|
|
||||||
await latestFolderTree.save();
|
|
||||||
|
|
||||||
// create new folder version
|
|
||||||
const newFolderVersion = new FolderVersion({
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment,
|
|
||||||
nodes: newFolderTree,
|
|
||||||
});
|
});
|
||||||
await newFolderVersion.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// update secret versions of restored secrets as not deleted
|
queue.push(...subQueue);
|
||||||
await SecretVersion.updateMany(
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fix any
|
||||||
|
const latestSecretVersionIds = await getLatestSecretVersionIds({
|
||||||
|
secretIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: fix any
|
||||||
|
const latestSecretVersions: any = (
|
||||||
|
await SecretVersion.find(
|
||||||
{
|
{
|
||||||
secret: {
|
_id: {
|
||||||
$in: Object.keys(oldSecretVersionsObj).map(
|
$in: latestSecretVersionIds.map((s) => s.versionId),
|
||||||
(sv) => oldSecretVersionsObj[sv].secret
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
"secret version"
|
||||||
isDeleted: false,
|
)
|
||||||
}
|
).reduce(
|
||||||
);
|
(accumulator, s) => ({
|
||||||
|
...accumulator,
|
||||||
|
[`${s.secret.toString()}`]: s,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
// take secret snapshot
|
const secDelQuery: Record<string, unknown> = {
|
||||||
await EESecretService.takeSecretSnapshot({
|
workspace: workspaceId,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
environment,
|
||||||
|
// undefined means root thus collect all secrets
|
||||||
|
};
|
||||||
|
if (folderId !== "root" && folderIds.length)
|
||||||
|
secDelQuery.folder = { $in: folderIds };
|
||||||
|
|
||||||
|
// delete existing secrets
|
||||||
|
await Secret.deleteMany(secDelQuery);
|
||||||
|
await Folder.deleteOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
});
|
||||||
|
|
||||||
|
// add secrets
|
||||||
|
const secrets = await Secret.insertMany(
|
||||||
|
Object.keys(oldSecretVersionsObj).map((sv) => {
|
||||||
|
const {
|
||||||
|
secret: secretId,
|
||||||
|
workspace,
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
environment,
|
||||||
|
secretBlindIndex,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
createdAt,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding,
|
||||||
|
folder: secFolderId,
|
||||||
|
} = oldSecretVersionsObj[sv];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_id: secretId,
|
||||||
|
version: latestSecretVersions[secretId.toString()].version + 1,
|
||||||
|
workspace,
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
environment,
|
||||||
|
secretBlindIndex: secretBlindIndex ?? undefined,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentCiphertext: "",
|
||||||
|
secretCommentIV: "",
|
||||||
|
secretCommentTag: "",
|
||||||
|
createdAt,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding,
|
||||||
|
folder: secFolderId,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// add secret versions
|
||||||
|
const secretV = await SecretVersion.insertMany(
|
||||||
|
secrets.map(
|
||||||
|
({
|
||||||
|
_id,
|
||||||
|
version,
|
||||||
|
workspace,
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
environment,
|
||||||
|
secretBlindIndex,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding,
|
||||||
|
folder: secFolderId,
|
||||||
|
}) => ({
|
||||||
|
_id: new Types.ObjectId(),
|
||||||
|
secret: _id,
|
||||||
|
version,
|
||||||
|
workspace,
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
environment,
|
||||||
|
isDeleted: false,
|
||||||
|
secretBlindIndex: secretBlindIndex ?? undefined,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding,
|
||||||
|
folder: secFolderId,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newFolderTree && latestFolderTree) {
|
||||||
|
// save the updated folder tree to the present one
|
||||||
|
newFolderTree.version = (latestFolderVersion?.nodes?.version || 0) + 1;
|
||||||
|
latestFolderTree._id = new Types.ObjectId();
|
||||||
|
latestFolderTree.isNew = true;
|
||||||
|
await latestFolderTree.save();
|
||||||
|
|
||||||
|
// create new folder version
|
||||||
|
const newFolderVersion = new FolderVersion({
|
||||||
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
folderId,
|
nodes: newFolderTree,
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to roll back secret snapshot",
|
|
||||||
});
|
});
|
||||||
|
await newFolderVersion.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update secret versions of restored secrets as not deleted
|
||||||
|
await SecretVersion.updateMany(
|
||||||
|
{
|
||||||
|
secret: {
|
||||||
|
$in: Object.keys(oldSecretVersionsObj).map(
|
||||||
|
(sv) => oldSecretVersionsObj[sv].secret
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isDeleted: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// take secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
folderId,
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
secrets,
|
secrets,
|
||||||
});
|
});
|
||||||
@@ -587,39 +559,30 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let logs;
|
const { workspaceId } = req.params;
|
||||||
try {
|
|
||||||
const { workspaceId } = req.params;
|
|
||||||
|
|
||||||
const offset: number = parseInt(req.query.offset as string);
|
const offset: number = parseInt(req.query.offset as string);
|
||||||
const limit: number = parseInt(req.query.limit as string);
|
const limit: number = parseInt(req.query.limit as string);
|
||||||
const sortBy: string = req.query.sortBy as string;
|
const sortBy: string = req.query.sortBy as string;
|
||||||
const userId: string = req.query.userId as string;
|
const userId: string = req.query.userId as string;
|
||||||
const actionNames: string = req.query.actionNames as string;
|
const actionNames: string = req.query.actionNames as string;
|
||||||
|
|
||||||
logs = await Log.find({
|
const logs = await Log.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
...(userId ? { user: userId } : {}),
|
...(userId ? { user: userId } : {}),
|
||||||
...(actionNames
|
...(actionNames
|
||||||
? {
|
? {
|
||||||
actionNames: {
|
actionNames: {
|
||||||
$in: actionNames.split(","),
|
$in: actionNames.split(","),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
})
|
})
|
||||||
.sort({ createdAt: sortBy === "recent" ? -1 : 1 })
|
.sort({ createdAt: sortBy === "recent" ? -1 : 1 })
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.populate("actions")
|
.populate("actions")
|
||||||
.populate("user serviceAccount serviceTokenData");
|
.populate("user serviceAccount serviceTokenData");
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: "Failed to get workspace logs",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
logs,
|
logs,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import NodeCache from 'node-cache';
|
import NodeCache from 'node-cache';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
import {
|
||||||
getLicenseKey,
|
getLicenseKey,
|
||||||
getLicenseServerKey,
|
getLicenseServerKey,
|
||||||
@@ -98,35 +97,29 @@ class EELicenseService {
|
|||||||
const licenseServerKey = await getLicenseServerKey();
|
const licenseServerKey = await getLicenseServerKey();
|
||||||
const licenseKey = await getLicenseKey();
|
const licenseKey = await getLicenseKey();
|
||||||
|
|
||||||
try {
|
if (licenseServerKey) {
|
||||||
if (licenseServerKey) {
|
// license server key is present -> validate it
|
||||||
// license server key is present -> validate it
|
const token = await refreshLicenseServerKeyToken()
|
||||||
const token = await refreshLicenseServerKeyToken()
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
this.instanceType = 'cloud';
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
if (token) {
|
||||||
|
this.instanceType = 'cloud';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (licenseKey) {
|
return;
|
||||||
// license key is present -> validate it
|
}
|
||||||
const token = await refreshLicenseKeyToken();
|
|
||||||
|
if (licenseKey) {
|
||||||
if (token) {
|
// license key is present -> validate it
|
||||||
const { data: { currentPlan } } = await licenseKeyRequest.get(
|
const token = await refreshLicenseKeyToken();
|
||||||
`${await getLicenseServerUrl()}/api/license/v1/plan`
|
|
||||||
);
|
if (token) {
|
||||||
|
const { data: { currentPlan } } = await licenseKeyRequest.get(
|
||||||
this.globalFeatureSet = currentPlan;
|
`${await getLicenseServerUrl()}/api/license/v1/plan`
|
||||||
this.instanceType = 'enterprise-self-hosted';
|
);
|
||||||
}
|
|
||||||
|
this.globalFeatureSet = currentPlan;
|
||||||
|
this.instanceType = 'enterprise-self-hosted';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
// case: self-hosted free
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,4 +128,4 @@ class EELicenseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new EELicenseService();
|
export default new EELicenseService();
|
||||||
|
|||||||
+171
-222
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
@@ -48,66 +47,59 @@ const handleOAuthExchangeHelper = async ({
|
|||||||
code: string;
|
code: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
}) => {
|
}) => {
|
||||||
let integrationAuth;
|
const bot = await Bot.findOne({
|
||||||
try {
|
workspace: workspaceId,
|
||||||
const bot = await Bot.findOne({
|
isActive: true
|
||||||
workspace: workspaceId,
|
});
|
||||||
isActive: true
|
|
||||||
|
if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange');
|
||||||
|
|
||||||
|
// exchange code for access and refresh tokens
|
||||||
|
const res = await exchangeCode({
|
||||||
|
integration,
|
||||||
|
code
|
||||||
|
});
|
||||||
|
|
||||||
|
const update: Update = {
|
||||||
|
workspace: workspaceId,
|
||||||
|
integration
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (integration) {
|
||||||
|
case INTEGRATION_VERCEL:
|
||||||
|
update.teamId = res.teamId;
|
||||||
|
break;
|
||||||
|
case INTEGRATION_NETLIFY:
|
||||||
|
update.accountId = res.accountId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||||
|
workspace: workspaceId,
|
||||||
|
integration
|
||||||
|
}, update, {
|
||||||
|
new: true,
|
||||||
|
upsert: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.refreshToken) {
|
||||||
|
// case: refresh token returned from exchange
|
||||||
|
// set integration auth refresh token
|
||||||
|
await setIntegrationAuthRefreshHelper({
|
||||||
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
|
refreshToken: res.refreshToken
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange');
|
|
||||||
|
if (res.accessToken) {
|
||||||
// exchange code for access and refresh tokens
|
// case: access token returned from exchange
|
||||||
const res = await exchangeCode({
|
// set integration auth access token
|
||||||
integration,
|
await setIntegrationAuthAccessHelper({
|
||||||
code
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
|
accessId: null,
|
||||||
|
accessToken: res.accessToken,
|
||||||
|
accessExpiresAt: res.accessExpiresAt
|
||||||
});
|
});
|
||||||
|
|
||||||
const update: Update = {
|
|
||||||
workspace: workspaceId,
|
|
||||||
integration
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (integration) {
|
|
||||||
case INTEGRATION_VERCEL:
|
|
||||||
update.teamId = res.teamId;
|
|
||||||
break;
|
|
||||||
case INTEGRATION_NETLIFY:
|
|
||||||
update.accountId = res.accountId;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
|
||||||
workspace: workspaceId,
|
|
||||||
integration
|
|
||||||
}, update, {
|
|
||||||
new: true,
|
|
||||||
upsert: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.refreshToken) {
|
|
||||||
// case: refresh token returned from exchange
|
|
||||||
// set integration auth refresh token
|
|
||||||
await setIntegrationAuthRefreshHelper({
|
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
|
||||||
refreshToken: res.refreshToken
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.accessToken) {
|
|
||||||
// case: access token returned from exchange
|
|
||||||
// set integration auth access token
|
|
||||||
await setIntegrationAuthAccessHelper({
|
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
|
||||||
accessId: null,
|
|
||||||
accessToken: res.accessToken,
|
|
||||||
accessExpiresAt: res.accessExpiresAt
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to handle OAuth2 code-token exchange')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return integrationAuth;
|
return integrationAuth;
|
||||||
@@ -125,47 +117,40 @@ const syncIntegrationsHelper = async ({
|
|||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
}) => {
|
}) => {
|
||||||
let integrations;
|
const integrations = await Integration.find({
|
||||||
try {
|
workspace: workspaceId,
|
||||||
integrations = await Integration.find({
|
...(environment ? {
|
||||||
workspace: workspaceId,
|
environment
|
||||||
...(environment ? {
|
} : {}),
|
||||||
environment
|
isActive: true,
|
||||||
} : {}),
|
app: { $ne: null }
|
||||||
isActive: true,
|
});
|
||||||
app: { $ne: null }
|
|
||||||
|
// for each workspace integration, sync/push secrets
|
||||||
|
// to that integration
|
||||||
|
for await (const integration of integrations) {
|
||||||
|
// get workspace, environment (shared) secrets
|
||||||
|
const secrets = await BotService.getSecrets({ // issue here?
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
environment: integration.environment
|
||||||
});
|
});
|
||||||
|
|
||||||
// for each workspace integration, sync/push secrets
|
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
||||||
// to that integration
|
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||||
for await (const integration of integrations) {
|
|
||||||
// get workspace, environment (shared) secrets
|
// get integration auth access token
|
||||||
const secrets = await BotService.getSecrets({ // issue here?
|
const access = await getIntegrationAuthAccessHelper({
|
||||||
workspaceId: integration.workspace,
|
integrationAuthId: integration.integrationAuth
|
||||||
environment: integration.environment
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
// sync secrets to integration
|
||||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
await syncSecrets({
|
||||||
|
integration,
|
||||||
// get integration auth access token
|
integrationAuth,
|
||||||
const access = await getIntegrationAuthAccessHelper({
|
secrets,
|
||||||
integrationAuthId: integration.integrationAuth
|
accessId: access.accessId === undefined ? null : access.accessId,
|
||||||
});
|
accessToken: access.accessToken
|
||||||
|
});
|
||||||
// sync secrets to integration
|
|
||||||
await syncSecrets({
|
|
||||||
integration,
|
|
||||||
integrationAuth,
|
|
||||||
secrets,
|
|
||||||
accessId: access.accessId === undefined ? null : access.accessId,
|
|
||||||
accessToken: access.accessToken
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to sync secrets to integrations');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,30 +163,18 @@ const syncIntegrationsHelper = async ({
|
|||||||
* @param {String} refreshToken - decrypted refresh token
|
* @param {String} refreshToken - decrypted refresh token
|
||||||
*/
|
*/
|
||||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||||
let refreshToken;
|
const integrationAuth = await IntegrationAuth
|
||||||
|
.findById(integrationAuthId)
|
||||||
try {
|
.select('+refreshCiphertext +refreshIV +refreshTag');
|
||||||
const integrationAuth = await IntegrationAuth
|
|
||||||
.findById(integrationAuthId)
|
|
||||||
.select('+refreshCiphertext +refreshIV +refreshTag');
|
|
||||||
|
|
||||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||||
|
|
||||||
refreshToken = await BotService.decryptSymmetric({
|
const refreshToken = await BotService.decryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
ciphertext: integrationAuth.refreshCiphertext as string,
|
ciphertext: integrationAuth.refreshCiphertext as string,
|
||||||
iv: integrationAuth.refreshIV as string,
|
iv: integrationAuth.refreshIV as string,
|
||||||
tag: integrationAuth.refreshTag as string
|
tag: integrationAuth.refreshTag as string
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
if(err instanceof RequestError)
|
|
||||||
throw err
|
|
||||||
else
|
|
||||||
throw new Error('Failed to get integration refresh token');
|
|
||||||
}
|
|
||||||
|
|
||||||
return refreshToken;
|
return refreshToken;
|
||||||
}
|
}
|
||||||
@@ -217,50 +190,40 @@ const syncIntegrationsHelper = async ({
|
|||||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||||
let accessId;
|
let accessId;
|
||||||
let accessToken;
|
let accessToken;
|
||||||
try {
|
const integrationAuth = await IntegrationAuth
|
||||||
const integrationAuth = await IntegrationAuth
|
.findById(integrationAuthId)
|
||||||
.findById(integrationAuthId)
|
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag');
|
||||||
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag');
|
|
||||||
|
|
||||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||||
|
|
||||||
accessToken = await BotService.decryptSymmetric({
|
accessToken = await BotService.decryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
ciphertext: integrationAuth.accessCiphertext as string,
|
ciphertext: integrationAuth.accessCiphertext as string,
|
||||||
iv: integrationAuth.accessIV as string,
|
iv: integrationAuth.accessIV as string,
|
||||||
tag: integrationAuth.accessTag as string
|
tag: integrationAuth.accessTag as string
|
||||||
});
|
});
|
||||||
|
|
||||||
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
||||||
// there is a access token expiration date
|
// there is a access token expiration date
|
||||||
// and refresh token to exchange with the OAuth2 server
|
// and refresh token to exchange with the OAuth2 server
|
||||||
|
|
||||||
if (integrationAuth.accessExpiresAt < new Date()) {
|
|
||||||
// access token is expired
|
|
||||||
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
|
||||||
accessToken = await exchangeRefresh({
|
|
||||||
integrationAuth,
|
|
||||||
refreshToken
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
|
if (integrationAuth.accessExpiresAt < new Date()) {
|
||||||
accessId = await BotService.decryptSymmetric({
|
// access token is expired
|
||||||
workspaceId: integrationAuth.workspace,
|
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
||||||
ciphertext: integrationAuth.accessIdCiphertext as string,
|
accessToken = await exchangeRefresh({
|
||||||
iv: integrationAuth.accessIdIV as string,
|
integrationAuth,
|
||||||
tag: integrationAuth.accessIdTag as string
|
refreshToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
|
||||||
Sentry.captureException(err);
|
accessId = await BotService.decryptSymmetric({
|
||||||
if(err instanceof RequestError)
|
workspaceId: integrationAuth.workspace,
|
||||||
throw err
|
ciphertext: integrationAuth.accessIdCiphertext as string,
|
||||||
else
|
iv: integrationAuth.accessIdIV as string,
|
||||||
throw new Error('Failed to get integration access token');
|
tag: integrationAuth.accessIdTag as string
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
@@ -285,34 +248,27 @@ const setIntegrationAuthRefreshHelper = async ({
|
|||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let integrationAuth;
|
let integrationAuth = await IntegrationAuth
|
||||||
try {
|
.findById(integrationAuthId);
|
||||||
integrationAuth = await IntegrationAuth
|
|
||||||
.findById(integrationAuthId);
|
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
const obj = await BotService.encryptSymmetric({
|
||||||
|
workspaceId: integrationAuth.workspace,
|
||||||
const obj = await BotService.encryptSymmetric({
|
plaintext: refreshToken
|
||||||
workspaceId: integrationAuth.workspace,
|
});
|
||||||
plaintext: refreshToken
|
|
||||||
});
|
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||||
|
_id: integrationAuthId
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
}, {
|
||||||
_id: integrationAuthId
|
refreshCiphertext: obj.ciphertext,
|
||||||
}, {
|
refreshIV: obj.iv,
|
||||||
refreshCiphertext: obj.ciphertext,
|
refreshTag: obj.tag,
|
||||||
refreshIV: obj.iv,
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
refreshTag: obj.tag,
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
}, {
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
new: true
|
||||||
}, {
|
});
|
||||||
new: true
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to set integration auth refresh token');
|
|
||||||
}
|
|
||||||
|
|
||||||
return integrationAuth;
|
return integrationAuth;
|
||||||
}
|
}
|
||||||
@@ -337,46 +293,39 @@ const setIntegrationAuthAccessHelper = async ({
|
|||||||
accessToken: string;
|
accessToken: string;
|
||||||
accessExpiresAt: Date | undefined;
|
accessExpiresAt: Date | undefined;
|
||||||
}) => {
|
}) => {
|
||||||
let integrationAuth;
|
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||||
try {
|
|
||||||
integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||||
|
workspaceId: integrationAuth.workspace,
|
||||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
plaintext: accessToken
|
||||||
|
});
|
||||||
|
|
||||||
|
let encryptedAccessIdObj;
|
||||||
|
if (accessId) {
|
||||||
|
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||||
workspaceId: integrationAuth.workspace,
|
workspaceId: integrationAuth.workspace,
|
||||||
plaintext: accessToken
|
plaintext: accessId
|
||||||
});
|
});
|
||||||
|
|
||||||
let encryptedAccessIdObj;
|
|
||||||
if (accessId) {
|
|
||||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
|
||||||
workspaceId: integrationAuth.workspace,
|
|
||||||
plaintext: accessId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
|
||||||
_id: integrationAuthId
|
|
||||||
}, {
|
|
||||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
|
||||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
|
||||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
|
||||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
|
||||||
accessIV: encryptedAccessTokenObj.iv,
|
|
||||||
accessTag: encryptedAccessTokenObj.tag,
|
|
||||||
accessExpiresAt,
|
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8
|
|
||||||
}, {
|
|
||||||
new: true
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to save integration auth access token');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
||||||
|
_id: integrationAuthId
|
||||||
|
}, {
|
||||||
|
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||||
|
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
||||||
|
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
||||||
|
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||||
|
accessIV: encryptedAccessTokenObj.iv,
|
||||||
|
accessTag: encryptedAccessTokenObj.tag,
|
||||||
|
accessExpiresAt,
|
||||||
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
|
keyEncoding: ENCODING_SCHEME_UTF8
|
||||||
|
}, {
|
||||||
|
new: true
|
||||||
|
});
|
||||||
|
|
||||||
return integrationAuth;
|
return integrationAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import { Types } from "mongoose";
|
||||||
import { Types } from 'mongoose';
|
import { Membership, Key } from "../models";
|
||||||
import { Membership, Key } from '../models';
|
import { MembershipNotFoundError, BadRequestError } from "../utils/errors";
|
||||||
import { MembershipNotFoundError, BadRequestError } from '../utils/errors';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
||||||
@@ -18,23 +17,23 @@ const validateMembership = async ({
|
|||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId | string;
|
userId: Types.ObjectId | string;
|
||||||
workspaceId: Types.ObjectId | string;
|
workspaceId: Types.ObjectId | string;
|
||||||
acceptedRoles?: Array<'admin' | 'member'>;
|
acceptedRoles?: Array<"admin" | "member">;
|
||||||
}) => {
|
}) => {
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
}).populate('workspace');
|
}).populate("workspace");
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw MembershipNotFoundError({
|
throw MembershipNotFoundError({
|
||||||
message: 'Failed to find workspace membership',
|
message: "Failed to find workspace membership",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedRoles) {
|
if (acceptedRoles) {
|
||||||
if (!acceptedRoles.includes(membership.role)) {
|
if (!acceptedRoles.includes(membership.role)) {
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message: 'Failed authorization for membership role',
|
message: "Failed authorization for membership role",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,15 +47,7 @@ const validateMembership = async ({
|
|||||||
* @return {Object} membership - membership
|
* @return {Object} membership - membership
|
||||||
*/
|
*/
|
||||||
const findMembership = async (queryObj: any) => {
|
const findMembership = async (queryObj: any) => {
|
||||||
let membership;
|
const membership = await Membership.findOne(queryObj);
|
||||||
try {
|
|
||||||
membership = await Membership.findOne(queryObj);
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to find membership');
|
|
||||||
}
|
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,31 +68,24 @@ const addMemberships = async ({
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
try {
|
const operations = userIds.map((userId, idx) => {
|
||||||
const operations = userIds.map((userId, idx) => {
|
return {
|
||||||
return {
|
updateOne: {
|
||||||
updateOne: {
|
filter: {
|
||||||
filter: {
|
user: userId,
|
||||||
user: userId,
|
workspace: workspaceId,
|
||||||
workspace: workspaceId,
|
role: roles[idx],
|
||||||
role: roles[idx],
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
user: userId,
|
|
||||||
workspace: workspaceId,
|
|
||||||
role: roles[idx],
|
|
||||||
},
|
|
||||||
upsert: true,
|
|
||||||
},
|
},
|
||||||
};
|
update: {
|
||||||
});
|
user: userId,
|
||||||
|
workspace: workspaceId,
|
||||||
await Membership.bulkWrite(operations as any);
|
role: roles[idx],
|
||||||
} catch (err) {
|
},
|
||||||
Sentry.setUser(null);
|
upsert: true,
|
||||||
Sentry.captureException(err);
|
},
|
||||||
throw new Error('Failed to add users to workspace');
|
};
|
||||||
}
|
});
|
||||||
|
await Membership.bulkWrite(operations as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,24 +94,17 @@ const addMemberships = async ({
|
|||||||
* @param {String} obj.membershipId - id of membership to delete
|
* @param {String} obj.membershipId - id of membership to delete
|
||||||
*/
|
*/
|
||||||
const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||||
let deletedMembership;
|
const deletedMembership = await Membership.findOneAndDelete({
|
||||||
try {
|
_id: membershipId,
|
||||||
deletedMembership = await Membership.findOneAndDelete({
|
});
|
||||||
_id: membershipId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// delete keys associated with the membership
|
// delete keys associated with the membership
|
||||||
if (deletedMembership?.user) {
|
if (deletedMembership?.user) {
|
||||||
// case: membership had a registered user
|
// case: membership had a registered user
|
||||||
await Key.deleteMany({
|
await Key.deleteMany({
|
||||||
receiver: deletedMembership.user,
|
receiver: deletedMembership.user,
|
||||||
workspace: deletedMembership.workspace,
|
workspace: deletedMembership.workspace,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to delete membership');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import handlebars from 'handlebars';
|
import handlebars from 'handlebars';
|
||||||
@@ -26,24 +25,19 @@ const sendMail = async ({
|
|||||||
substitutions: any;
|
substitutions: any;
|
||||||
}) => {
|
}) => {
|
||||||
if (await getSmtpConfigured()) {
|
if (await getSmtpConfigured()) {
|
||||||
try {
|
const html = fs.readFileSync(
|
||||||
const html = fs.readFileSync(
|
path.resolve(__dirname, '../templates/' + template),
|
||||||
path.resolve(__dirname, '../templates/' + template),
|
'utf8'
|
||||||
'utf8'
|
);
|
||||||
);
|
const temp = handlebars.compile(html);
|
||||||
const temp = handlebars.compile(html);
|
const htmlToSend = temp(substitutions);
|
||||||
const htmlToSend = temp(substitutions);
|
|
||||||
|
|
||||||
await smtpTransporter.sendMail({
|
await smtpTransporter.sendMail({
|
||||||
from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`,
|
from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`,
|
||||||
to: recipients.join(', '),
|
to: recipients.join(', '),
|
||||||
subject: subjectLine,
|
subject: subjectLine,
|
||||||
html: htmlToSend
|
html: htmlToSend
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { IUser } from '../models';
|
import { IUser } from '../models';
|
||||||
import { createOrganization } from './organization';
|
import { createOrganization } from './organization';
|
||||||
import { addMembershipsOrg } from './membershipOrg';
|
import { addMembershipsOrg } from './membershipOrg';
|
||||||
@@ -15,28 +14,20 @@ import { TOKEN_EMAIL_CONFIRMATION } from '../variables';
|
|||||||
* @returns {Boolean} success - whether or not operation was successful
|
* @returns {Boolean} success - whether or not operation was successful
|
||||||
*/
|
*/
|
||||||
const sendEmailVerification = async ({ email }: { email: string }) => {
|
const sendEmailVerification = async ({ email }: { email: string }) => {
|
||||||
try {
|
const token = await TokenService.createToken({
|
||||||
const token = await TokenService.createToken({
|
type: TOKEN_EMAIL_CONFIRMATION,
|
||||||
type: TOKEN_EMAIL_CONFIRMATION,
|
email
|
||||||
email
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// send mail
|
// send mail
|
||||||
await sendMail({
|
await sendMail({
|
||||||
template: 'emailVerification.handlebars',
|
template: 'emailVerification.handlebars',
|
||||||
subjectLine: 'Infisical confirmation code',
|
subjectLine: 'Infisical confirmation code',
|
||||||
recipients: [email],
|
recipients: [email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
code: token
|
code: token
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error(
|
|
||||||
"Ouch. We weren't able to send your email verification code"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,17 +43,11 @@ const checkEmailVerification = async ({
|
|||||||
email: string;
|
email: string;
|
||||||
code: string;
|
code: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
await TokenService.validateToken({
|
||||||
await TokenService.validateToken({
|
type: TOKEN_EMAIL_CONFIRMATION,
|
||||||
type: TOKEN_EMAIL_CONFIRMATION,
|
email,
|
||||||
email,
|
token: code
|
||||||
token: code
|
});
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error("Oops. We weren't able to verify");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
import {
|
||||||
Workspace,
|
Workspace,
|
||||||
Bot,
|
Bot,
|
||||||
@@ -23,31 +22,24 @@ const createWorkspace = async ({
|
|||||||
name: string;
|
name: string;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
}) => {
|
}) => {
|
||||||
let workspace;
|
// create workspace
|
||||||
try {
|
const workspace = await new Workspace({
|
||||||
// create workspace
|
name,
|
||||||
workspace = await new Workspace({
|
organization: organizationId,
|
||||||
name,
|
autoCapitalization: true
|
||||||
organization: organizationId,
|
}).save();
|
||||||
autoCapitalization: true
|
|
||||||
}).save();
|
// initialize bot for workspace
|
||||||
|
await createBot({
|
||||||
// initialize bot for workspace
|
name: 'Infisical Bot',
|
||||||
await createBot({
|
workspaceId: workspace._id
|
||||||
name: 'Infisical Bot',
|
});
|
||||||
workspaceId: workspace._id
|
|
||||||
});
|
// initialize blind index salt for workspace
|
||||||
|
await SecretService.createSecretBlindIndexData({
|
||||||
// initialize blind index salt for workspace
|
workspaceId: workspace._id
|
||||||
await SecretService.createSecretBlindIndexData({
|
});
|
||||||
workspaceId: workspace._id
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to create workspace');
|
|
||||||
}
|
|
||||||
|
|
||||||
return workspace;
|
return workspace;
|
||||||
};
|
};
|
||||||
@@ -59,25 +51,19 @@ const createWorkspace = async ({
|
|||||||
* @param {String} obj.id - id of workspace to delete
|
* @param {String} obj.id - id of workspace to delete
|
||||||
*/
|
*/
|
||||||
const deleteWorkspace = async ({ id }: { id: string }) => {
|
const deleteWorkspace = async ({ id }: { id: string }) => {
|
||||||
try {
|
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
|
});
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to delete workspace');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
+1075
-1175
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user