🧑‍💻 refactor: Secure Field Selection for 2FA & API Build Sourcemap (#9087)

* refactor: `packages/api` build scripts for better inline debugging

* refactor: Explicitly select secure fields as no longer returned by default, exclude backupCodes from user data retrieval in authentication and 2FA processes

* refactor: Backup Codes UI to not expect backup codes, only regeneration

* refactor: Ensure secure fields are deleted from user data in getUserController
This commit is contained in:
Danny Avila 2025-08-15 18:55:49 -04:00 committed by GitHub
parent 50b7bd6643
commit 3547873bc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 82 additions and 31 deletions

View file

@ -84,7 +84,7 @@ const refreshController = async (req, res) => {
}
try {
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const user = await getUserById(payload.id, '-password -__v -totpSecret');
const user = await getUserById(payload.id, '-password -__v -totpSecret -backupCodes');
if (!user) {
return res.status(401).redirect('/login');
}

View file

@ -47,7 +47,7 @@ const verify2FA = async (req, res) => {
try {
const userId = req.user.id;
const { token, backupCode } = req.body;
const user = await getUserById(userId);
const user = await getUserById(userId, '_id totpSecret backupCodes');
if (!user || !user.totpSecret) {
return res.status(400).json({ message: '2FA not initiated' });
@ -79,7 +79,7 @@ const confirm2FA = async (req, res) => {
try {
const userId = req.user.id;
const { token } = req.body;
const user = await getUserById(userId);
const user = await getUserById(userId, '_id totpSecret');
if (!user || !user.totpSecret) {
return res.status(400).json({ message: '2FA not initiated' });
@ -105,7 +105,7 @@ const disable2FA = async (req, res) => {
try {
const userId = req.user.id;
const { token, backupCode } = req.body;
const user = await getUserById(userId);
const user = await getUserById(userId, '_id totpSecret backupCodes');
if (!user || !user.totpSecret) {
return res.status(400).json({ message: '2FA is not setup for this user' });

View file

@ -24,7 +24,13 @@ const { getMCPManager } = require('~/config');
const getUserController = async (req, res) => {
/** @type {MongoUser} */
const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user };
/**
* These fields should not exist due to secure field selection, but deletion
* is done in case of alternate database incompatibility with Mongo API
* */
delete userData.password;
delete userData.totpSecret;
delete userData.backupCodes;
if (req.app.locals.fileStrategy === FileSources.s3 && userData.avatar) {
const avatarNeedsRefresh = needsRefresh(userData.avatar, 3600);
if (!avatarNeedsRefresh) {

View file

@ -22,10 +22,11 @@ const verify2FAWithTempToken = async (req, res) => {
try {
payload = jwt.verify(tempToken, process.env.JWT_SECRET);
} catch (err) {
logger.error('Failed to verify temporary token:', err);
return res.status(401).json({ message: 'Invalid or expired temporary token' });
}
const user = await getUserById(payload.userId);
const user = await getUserById(payload.userId, '+totpSecret +backupCodes');
if (!user || !user.twoFactorEnabled) {
return res.status(400).json({ message: '2FA is not enabled for this user' });
}
@ -42,11 +43,11 @@ const verify2FAWithTempToken = async (req, res) => {
return res.status(401).json({ message: 'Invalid 2FA code or backup code' });
}
// Prepare user data to return (omit sensitive fields).
const userData = user.toObject ? user.toObject() : { ...user };
delete userData.password;
delete userData.__v;
delete userData.password;
delete userData.totpSecret;
delete userData.backupCodes;
userData.id = user._id.toString();
const authToken = await setAuthTokens(user._id, res);