Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/server/middleware/roles/admin.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { SystemRoles } = require('librechat-data-provider');
const { checkAdminAccess } = require('~/server/stripe/hardcodedAdminUtils');

function checkAdmin(req, res, next) {
try {
if (req.user.role !== SystemRoles.ADMIN) {
return res.status(403).json({ message: 'Forbidden' });
if (checkAdminAccess(req.user)) {
return next();
}
next();
return res.status(403).json({ message: 'Forbidden' });
} catch (error) {
res.status(500).json({ message: 'Internal Server Error' });
}
Expand Down
73 changes: 73 additions & 0 deletions api/server/stripe/hardcodedAdminUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const { SystemRoles } = require('librechat-data-provider');
const { logger } = require('~/config');

/**
* Helper function to check if a user is a hardcoded admin (username-only)
*

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe add something to comment like:

e.g. HARDCODED_ADMIN_USERNAMES=colinlin,mattmueller

* @param {string} username - Username to check
* @returns {boolean} - True if user is a hardcoded admin
*/
function isHardcodedAdmin(username) {
if (!username) {
logger.error('[Stripe:isHardcodedAdmin] message=No username found');
return false;
}

// example: HARDCODED_ADMIN_USERNAMES=username1,username2,username3
const hardcodedAdminUsernames = process.env.HARDCODED_ADMIN_USERNAMES;
if (hardcodedAdminUsernames) {
const adminUsernames = hardcodedAdminUsernames.split(',').map(username => username.trim().toLowerCase());
if (username && adminUsernames.includes(username.toLowerCase())) {
return true;
}
}

return false;
}

/**
* Modifies the user object to ensure that only hardcoded admins have admin role
*
* @param {Object} user - User object
* @returns {Object} - User object with modified role
*/
function ensureHardcodedAdminRole(user) {
if (!user) {
logger.error('[Stripe:ensureHardcodedAdminRole] message=No user found');
return user;
}

if (isHardcodedAdmin(user.username)) {
user.role = SystemRoles.ADMIN;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log here as well? It seems like checkAdminAccess is logged, so might be double-logging in the normal case, but we also use this function directly below

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kept user null check and changed isHardcodedAdmin to accept a username so no redundant null check logs

} else {
// User is NOT a hardcoded admin → force role to USER (even if DB says ADMIN)
user.role = SystemRoles.USER;
}

return user;
}

/**
* Checks if a user should have admin access
* Fallback to database role check when hardcoded admin is not enabled
*
* @param {Object} user - User object
* @returns {boolean} - True if user should have admin access
*/
function checkAdminAccess(user) {
if (!user) {
logger.error('[Stripe:checkAdminAccess] message=No user found');
return false;
}
if (process.env.HARDCODED_ADMIN_USERNAMES) {
logger.info('[Stripe:checkAdminAccess] message=Hardcoded admin mode enabled');
return isHardcodedAdmin(user.username);
}
// Fallback to database role check when hardcoded admin is not enabled
return user.role === SystemRoles.ADMIN;
}

module.exports = {
ensureHardcodedAdminRole,
checkAdminAccess,
};
4 changes: 4 additions & 0 deletions api/strategies/forwardedAuthStrategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { SystemRoles } = require('librechat-data-provider');
const passportCustom = require('passport-custom');
const { getUserById, updateUser, findUser, createUser, countUsers } = require('~/models');
const { logger } = require('~/config');
const { ensureHardcodedAdminRole } = require('~/server/stripe/hardcodedAdminUtils');

/**
* Strategy for authentication using forwarded HTTP headers from a reverse proxy
Expand Down Expand Up @@ -87,6 +88,9 @@ const forwardedAuthStrategy = () => {
// Add id property for consistency with other auth strategies
user.id = user._id.toString();

// <stripe>
user = ensureHardcodedAdminRole(user);
// </stripe>
return done(null, user);
} catch (err) {
logger.error('[forwardedAuthStrategy] Error:', err);
Expand Down