diff --git a/api/server/controllers/auth/AdminLoginController.js b/api/server/controllers/auth/AdminLoginController.js deleted file mode 100644 index 6744444570..0000000000 --- a/api/server/controllers/auth/AdminLoginController.js +++ /dev/null @@ -1,77 +0,0 @@ -const { logger, signPayload } = require('@librechat/data-schemas'); -const { generate2FATempToken } = require('~/server/services/twoFactorService'); -const { setAuthTokens } = require('~/server/services/AuthService'); - -/** - * Generates admin-specific JWT token with isAdmin claim - * @param {Object} user - User object from database - * @returns {Promise} - JWT token - */ -const generateAdminToken = async (user) => { - if (!user) { - throw new Error('No user provided'); - } - - let expires = 1000 * 60 * 15; // 15 minutes default - - if (process.env.SESSION_EXPIRY !== undefined && process.env.SESSION_EXPIRY !== '') { - try { - const evaluated = eval(process.env.SESSION_EXPIRY); - if (evaluated) { - expires = evaluated; - } - } catch (error) { - logger.warn('Invalid SESSION_EXPIRY expression, using default:', error); - } - } - - return await signPayload({ - payload: { - id: user._id, - username: user.username, - provider: user.provider, - email: user.email, - isAdmin: true, // Admin-specific claim - }, - secret: process.env.JWT_SECRET, - expirationTime: expires / 1000, - }); -}; - -/** - * Admin login controller - handles authentication for admin users - * Returns admin-specific JWT with isAdmin claim - */ -const adminLoginController = async (req, res) => { - try { - if (!req.user) { - return res.status(400).json({ message: 'Invalid credentials' }); - } - - // User role validation is already done in requireAdminAuth middleware - - // Handle 2FA if enabled - if (req.user.twoFactorEnabled) { - const tempToken = generate2FATempToken(req.user._id); - return res.status(200).json({ twoFAPending: true, tempToken }); - } - - const { password: _p, totpSecret: _t, __v, ...user } = req.user; - user.id = user._id.toString(); - - // Generate admin-specific token - const token = await generateAdminToken(req.user); - - // Set standard auth cookies (refreshToken, etc.) - await setAuthTokens(req.user._id, res); - - return res.status(200).send({ token, user, isAdmin: true }); - } catch (err) { - logger.error('[adminLoginController]', err); - return res.status(500).json({ message: 'Something went wrong' }); - } -}; - -module.exports = { - adminLoginController, -}; diff --git a/api/server/controllers/auth/AdminVerifyController.js b/api/server/controllers/auth/AdminVerifyController.js deleted file mode 100644 index ff835316a3..0000000000 --- a/api/server/controllers/auth/AdminVerifyController.js +++ /dev/null @@ -1,39 +0,0 @@ -const { logger } = require('@librechat/data-schemas'); -const { SystemRoles } = require('librechat-data-provider'); - -/** - * Admin token verification controller - * Verifies JWT token and returns user data if valid and has admin role - * Used by admin panel to verify authentication status - */ -const adminVerifyController = async (req, res) => { - try { - // User is already authenticated via requireAdminJwtAuth middleware - if (!req.user) { - return res.status(401).json({ message: 'Unauthorized' }); - } - - // Double-check admin role (redundant but ensures security) - if (!req.user.role || req.user.role !== SystemRoles.ADMIN) { - logger.warn('[adminVerifyController] Non-admin user attempting to verify:', req.user.email); - return res.status(403).json({ message: 'Access denied: Admin privileges required' }); - } - - // Return user data without sensitive fields - const { password: _p, totpSecret: _t, __v, ...user } = req.user; - user.id = user._id.toString(); - - return res.status(200).json({ - valid: true, - user, - isAdmin: true, - }); - } catch (err) { - logger.error('[adminVerifyController]', err); - return res.status(500).json({ message: 'Something went wrong' }); - } -}; - -module.exports = { - adminVerifyController, -}; diff --git a/api/server/middleware/index.js b/api/server/middleware/index.js index 7d8e000e18..6f382f5e0a 100644 --- a/api/server/middleware/index.js +++ b/api/server/middleware/index.js @@ -14,6 +14,7 @@ const requireLdapAuth = require('./requireLdapAuth'); const abortMiddleware = require('./abortMiddleware'); const checkInviteUser = require('./checkInviteUser'); const requireJwtAuth = require('./requireJwtAuth'); +const requireAdmin = require('./requireAdmin'); const configMiddleware = require('./config/app'); const validateModel = require('./validateModel'); const moderateText = require('./moderateText'); @@ -40,8 +41,7 @@ module.exports = { moderateText, validateModel, requireJwtAuth, - requireAdminAuth, - requireAdminJwtAuth, + requireAdmin, checkInviteUser, requireLdapAuth, requireLocalAuth, diff --git a/api/server/middleware/requireAdmin.js b/api/server/middleware/requireAdmin.js new file mode 100644 index 0000000000..12d1b751d9 --- /dev/null +++ b/api/server/middleware/requireAdmin.js @@ -0,0 +1,22 @@ +const { SystemRoles } = require('librechat-data-provider'); +const { logger } = require('~/config'); + +/** + * Middleware to check if authenticated user has admin role + * Should be used AFTER authentication middleware (requireJwtAuth, requireLocalAuth, etc.) + */ +const requireAdmin = (req, res, next) => { + if (!req.user) { + logger.warn('[requireAdmin] No user found in request'); + return res.status(401).json({ message: 'Authentication required' }); + } + + if (!req.user.role || req.user.role !== SystemRoles.ADMIN) { + logger.debug('[requireAdmin] Access denied for non-admin user:', req.user.email); + return res.status(403).json({ message: 'Access denied: Admin privileges required' }); + } + + next(); +}; + +module.exports = requireAdmin; diff --git a/api/server/middleware/requireAdminAuth.js b/api/server/middleware/requireAdminAuth.js deleted file mode 100644 index 03e2b2cf17..0000000000 --- a/api/server/middleware/requireAdminAuth.js +++ /dev/null @@ -1,35 +0,0 @@ -const passport = require('passport'); -const { logger } = require('@librechat/data-schemas'); -const { SystemRoles } = require('librechat-data-provider'); - -/** - * Middleware for admin authentication using local strategy - * Validates credentials and ensures user has admin role - */ -const requireAdminAuth = (req, res, next) => { - passport.authenticate('local', (err, user, info) => { - if (err) { - logger.error('[requireAdminAuth] Error at passport.authenticate:', err); - return next(err); - } - if (!user) { - logger.debug('[requireAdminAuth] Error: No user'); - return res.status(404).send(info); - } - if (info && info.message) { - logger.debug('[requireAdminAuth] Error: ' + info.message); - return res.status(422).send({ message: info.message }); - } - - // Check if user has admin role - if (!user.role || user.role !== SystemRoles.ADMIN) { - logger.debug('[requireAdminAuth] Error: User is not an admin'); - return res.status(403).send({ message: 'Access denied: Admin privileges required' }); - } - - req.user = user; - next(); - })(req, res, next); -}; - -module.exports = requireAdminAuth; diff --git a/api/server/middleware/requireAdminJwtAuth.js b/api/server/middleware/requireAdminJwtAuth.js deleted file mode 100644 index 346799b933..0000000000 --- a/api/server/middleware/requireAdminJwtAuth.js +++ /dev/null @@ -1,42 +0,0 @@ -const cookies = require('cookie'); -const passport = require('passport'); -const { isEnabled } = require('@librechat/api'); -const { logger } = require('@librechat/data-schemas'); -const { SystemRoles } = require('librechat-data-provider'); - -/** - * Custom Middleware to handle JWT authentication for admin endpoints - * Validates JWT token and ensures user has admin role - */ -const requireAdminJwtAuth = (req, res, next) => { - // Check if token provider is specified in cookies - const cookieHeader = req.headers.cookie; - const tokenProvider = cookieHeader ? cookies.parse(cookieHeader).token_provider : null; - - // Use OpenID authentication if token provider is OpenID and OPENID_REUSE_TOKENS is enabled - const authStrategy = - tokenProvider === 'openid' && isEnabled(process.env.OPENID_REUSE_TOKENS) ? 'openidJwt' : 'jwt'; - - passport.authenticate(authStrategy, { session: false }, (err, user, _info) => { - if (err) { - logger.error('[requireAdminJwtAuth] Authentication error:', err); - return res.status(500).json({ message: 'Authentication error' }); - } - - if (!user) { - logger.debug('[requireAdminJwtAuth] No user found'); - return res.status(401).json({ message: 'Unauthorized' }); - } - - // Check if user has admin role - if (!user.role || user.role !== SystemRoles.ADMIN) { - logger.debug('[requireAdminJwtAuth] User is not an admin:', user.email); - return res.status(403).json({ message: 'Access denied: Admin privileges required' }); - } - - req.user = user; - next(); - })(req, res, next); -}; - -module.exports = requireAdminJwtAuth; diff --git a/api/server/routes/admin/auth.js b/api/server/routes/admin/auth.js index 003784635a..ac3cf0f0e6 100644 --- a/api/server/routes/admin/auth.js +++ b/api/server/routes/admin/auth.js @@ -1,25 +1,40 @@ const express = require('express'); -const { adminVerifyController } = require('~/server/controllers/auth/AdminVerifyController'); -const { adminLoginController } = require('~/server/controllers/auth/AdminLoginController'); +const { loginController } = require('~/server/controllers/auth/LoginController'); +const { getAppConfig } = require('~/server/services/Config'); +const { createSetBalanceConfig } = require('@librechat/api'); const middleware = require('~/server/middleware'); +const { Balance } = require('~/db/models'); + +const setBalanceConfig = createSetBalanceConfig({ + getAppConfig, + Balance, +}); const router = express.Router(); -// Admin local authentication route +// Admin local authentication route - reuses main login controller router.post( '/login/local', middleware.logHeaders, middleware.loginLimiter, middleware.checkBan, - middleware.requireAdminAuth, // Uses local auth strategy + admin role validation - adminLoginController, + middleware.requireLocalAuth, // Standard local auth + middleware.requireAdmin, // Then check if user is admin + setBalanceConfig, + loginController, // Reuse existing login controller ); -// Admin token verification endpoint +// Admin token verification endpoint - simple JWT verify + admin check router.get( '/verify', - middleware.requireAdminJwtAuth, // Validates JWT + admin role - adminVerifyController, + middleware.requireJwtAuth, // Standard JWT auth + middleware.requireAdmin, // Then check if user is admin + (req, res) => { + // Simple response - user is already verified by middleware + const { password: _p, totpSecret: _t, __v, ...user } = req.user; + user.id = user._id.toString(); + res.status(200).json({ user }); + }, ); // TODO: Future OAuth/OpenID routes will be added here