Refactoring functions into services, helper functions, hooks, patch bugs

This commit is contained in:
Tuan Dang
2023-02-14 17:38:58 +07:00
parent 13b1805d04
commit e1ad8fbee8
30 changed files with 383 additions and 360 deletions
+28 -11
View File
@@ -269,19 +269,36 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
secure: NODE_ENV === 'production' ? true : false
});
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
const resObj: VerifyMfaTokenRes = {
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
// case: user does not have MFA enabled
// return (access) token in response
return res.status(200).send({
encryptionVersion: user.encryptionVersion,
protectedKey: user.protectedKey ?? null,
protectedKeyIV: user.protectedKeyIV ?? null,
protectedKeyTag: user.protectedKeyTag ?? null,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
});
return res.status(200).send(resObj);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
+20 -7
View File
@@ -7,6 +7,7 @@ import {
} from '../../helpers/signup';
import { issueAuthTokens } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables';
import { NODE_ENV } from '../../config';
import axios from 'axios';
/**
@@ -105,7 +106,6 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
});
token = tokens.token;
refreshToken = tokens.refreshToken;
// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
@@ -121,6 +121,14 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
},
});
}
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -132,8 +140,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
token
});
};
@@ -219,7 +226,14 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
});
token = tokens.token;
refreshToken = tokens.refreshToken;
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -227,11 +241,10 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
token
});
};
+56 -39
View File
@@ -12,7 +12,7 @@ import {
import {
SALT_ROUNDS
} from '../config';
import { UnauthorizedRequestError } from '../utils/errors';
import { ForbiddenRequestError } from '../utils/errors';
/**
* Create and store a token in the database for purpose [type]
@@ -65,29 +65,45 @@ const createTokenHelper = async ({
break;
}
interface Query {
interface TokenDataQuery {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface TokenDataUpdate {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
expiresAt: Date;
}
const query: Query = { type };
const query: TokenDataQuery = { type };
const update: TokenDataUpdate = {
type,
tokenHash: await bcrypt.hash(token, SALT_ROUNDS),
expiresAt
}
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId }
if (email) {
query.email = email;
update.email = email;
}
if (phoneNumber) {
query.phoneNumber = phoneNumber;
update.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId
update.organization = organizationId
}
await TokenData.findOneAndUpdate(
query,
{
type,
email,
phoneNumber,
organization: organizationId,
tokenHash: await bcrypt.hash(token, SALT_ROUNDS),
expiresAt
},
update,
{
new: true,
upsert: true
@@ -123,37 +139,38 @@ const validateTokenHelper = async ({
organizationId?: Types.ObjectId;
token: string;
}) => {
try {
interface Query {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface Query {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
const query: Query = { type };
const query: Query = { type };
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId; }
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId; }
const tokenData = await TokenData.findOneAndDelete(query);
if (!tokenData) throw new Error('Failed to find token to validate');
if (tokenData.expiresAt < new Date()) throw new Error('Token has expired');
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) throw UnauthorizedRequestError({
const tokenData = await TokenData.findOne(query).select('+tokenHash');
if (!tokenData) throw new Error('Failed to find token to validate');
if (tokenData.expiresAt < new Date()) {
await TokenData.findByIdAndDelete(tokenData._id);
throw ForbiddenRequestError({
message: 'Failed token data validation due to token is no longer valid'
});
}
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) {
throw ForbiddenRequestError({
message: 'Failed token data validation due to incorrect token'
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(
"Failed to validate token data"
);
}
await TokenData.findByIdAndDelete(tokenData._id);
}
export {
@@ -1,11 +1,12 @@
import { ErrorRequestHandler } from "express";
import * as Sentry from '@sentry/node';
import { InternalServerError } from "../utils/errors";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError";
import { NODE_ENV } from "../config";
import { TokenExpiredError } from 'jsonwebtoken';
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
if (res.headersSent) return next();
@@ -16,7 +17,9 @@ export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | E
}
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if (!(error instanceof RequestError)) {
if (error instanceof TokenExpiredError) {
error = UnauthorizedRequestError({ stack: error.stack, message: 'Token expired' });
} else if (!(error instanceof RequestError)) {
error = InternalServerError({ context: { exception: error.message }, stack: error.stack })
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
}