Fix merge conflicts

This commit is contained in:
Tuan Dang
2023-01-04 21:31:58 +07:00
41 changed files with 380 additions and 220 deletions
+60 -9
View File
@@ -3,22 +3,25 @@ import * as Sentry from '@sentry/node';
import bcrypt from 'bcrypt';
import {
User,
ServiceTokenData
ServiceTokenData,
APIKeyData
} from '../models';
import {
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_LIFETIME,
JWT_REFRESH_SECRET,
SALT_ROUNDS
JWT_REFRESH_SECRET
} from '../config';
import {
AccountNotFoundError,
ServiceTokenDataNotFoundError,
UnauthorizedRequestError,
BadRequestError
APIKeyDataNotFoundError,
UnauthorizedRequestError
} from '../utils/errors';
// TODO 1: check if API key works
// TODO 2: optimize middleware
/**
* Validate that auth token value [authTokenValue] falls under one of
* accepted auth modes [acceptedAuthModes].
@@ -40,6 +43,9 @@ const validateAuthMode = ({
case 'st':
authMode = 'serviceToken';
break;
case 'ak':
authMode = 'apiKey';
break;
default:
authMode = 'jwt';
break;
@@ -106,9 +112,11 @@ const getAuthSTDPayload = async ({
// TODO: optimize double query
serviceTokenData = await ServiceTokenData
.findById(TOKEN_IDENTIFIER, 'secretHash expiresAt');
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt');
if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
if (!serviceTokenData) {
throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
// case: service token expired
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
throw UnauthorizedRequestError({
@@ -116,8 +124,6 @@ const getAuthSTDPayload = async ({
});
}
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({
message: 'Failed to authenticate service token'
@@ -136,6 +142,50 @@ const getAuthSTDPayload = async ({
return serviceTokenData;
}
/**
* Return API key data payload corresponding to API key [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - API key value
* @returns {APIKeyData} apiKeyData - API key data
*/
const getAuthAPIKeyPayload = async ({
authTokenValue
}: {
authTokenValue: string;
}) => {
let user;
try {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
const apiKeyData = await APIKeyData
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
.populate('user', '+publicKey');
if (!apiKeyData) {
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
// case: API key expired
await APIKeyData.findByIdAndDelete(apiKeyData._id);
throw UnauthorizedRequestError({
message: 'Failed to authenticate expired API key'
});
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({
message: 'Failed to authenticate API key'
});
user = apiKeyData.user;
} catch (err) {
throw UnauthorizedRequestError({
message: 'Failed to authenticate API key'
});
}
return user;
}
/**
* Return newly issued (JWT) auth and refresh tokens to user with id [userId]
* @param {Object} obj
@@ -229,6 +279,7 @@ export {
validateAuthMode,
getAuthUserPayload,
getAuthSTDPayload,
getAuthAPIKeyPayload,
createToken,
issueTokens,
clearTokens
+4 -16
View File
@@ -3,7 +3,7 @@ import { Membership, Key } from '../models';
/**
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
* and has at least one of the roles in [acceptedRoles] and statuses in [acceptedStatuses]
* and has at least one of the roles in [acceptedRoles]
* @param {Object} obj
* @param {String} obj.userId - id of user to validate
* @param {String} obj.workspaceId - id of workspace
@@ -12,12 +12,10 @@ const validateMembership = async ({
userId,
workspaceId,
acceptedRoles,
acceptedStatuses
}: {
userId: string;
workspaceId: string;
acceptedRoles: string[];
acceptedStatuses: string[];
}) => {
let membership;
@@ -33,11 +31,6 @@ const validateMembership = async ({
if (!acceptedRoles.includes(membership.role)) {
throw new Error('Failed to validate membership role');
}
if (!acceptedStatuses.includes(membership.status)) {
throw new Error('Failed to validate membership status');
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -72,18 +65,15 @@ const findMembership = async (queryObj: any) => {
* @param {String[]} obj.userIds - id of users.
* @param {String} obj.workspaceId - id of workspace.
* @param {String[]} obj.roles - roles of users.
* @param {String[]} obj.statuses - statuses of users.
*/
const addMemberships = async ({
userIds,
workspaceId,
roles,
statuses
roles
}: {
userIds: string[];
workspaceId: string;
roles: string[];
statuses: string[];
}): Promise<void> => {
try {
const operations = userIds.map((userId, idx) => {
@@ -92,14 +82,12 @@ const addMemberships = async ({
filter: {
user: userId,
workspace: workspaceId,
role: roles[idx],
status: statuses[idx]
role: roles[idx]
},
update: {
user: userId,
workspace: workspaceId,
role: roles[idx],
status: statuses[idx]
role: roles[idx]
},
upsert: true
}
+2 -2
View File
@@ -3,7 +3,7 @@ import rateLimit from 'express-rate-limit';
// 300 requests per 15 minutes
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 400,
max: 450,
standardHeaders: true,
legacyHeaders: false,
skip: (request) => request.path === '/healthcheck'
@@ -20,7 +20,7 @@ const signupLimiter = rateLimit({
// 10 requests per hour
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 20,
max: 25,
standardHeaders: true,
legacyHeaders: false
});
+2 -3
View File
@@ -5,7 +5,7 @@ import { createOrganization } from './organization';
import { addMembershipsOrg } from './membershipOrg';
import { createWorkspace } from './workspace';
import { addMemberships } from './membership';
import { OWNER, ADMIN, ACCEPTED, GRANTED } from '../variables';
import { OWNER, ADMIN, ACCEPTED } from '../variables';
import { sendMail } from '../helpers/nodemailer';
/**
@@ -113,8 +113,7 @@ const initializeDefaultOrg = async ({
await addMemberships({
userIds: [user._id.toString()],
workspaceId: workspace._id.toString(),
roles: [ADMIN],
statuses: [GRANTED]
roles: [ADMIN]
});
} catch (err) {
throw new Error('Failed to initialize default organization and workspace');