mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-21 10:50:14 +01:00
refactor: re-use logic for admin routes
This commit is contained in:
parent
fbe0def2fa
commit
a1f9f3dd39
7 changed files with 47 additions and 203 deletions
|
|
@ -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<string>} - 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,
|
|
||||||
};
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
@ -14,6 +14,7 @@ const requireLdapAuth = require('./requireLdapAuth');
|
||||||
const abortMiddleware = require('./abortMiddleware');
|
const abortMiddleware = require('./abortMiddleware');
|
||||||
const checkInviteUser = require('./checkInviteUser');
|
const checkInviteUser = require('./checkInviteUser');
|
||||||
const requireJwtAuth = require('./requireJwtAuth');
|
const requireJwtAuth = require('./requireJwtAuth');
|
||||||
|
const requireAdmin = require('./requireAdmin');
|
||||||
const configMiddleware = require('./config/app');
|
const configMiddleware = require('./config/app');
|
||||||
const validateModel = require('./validateModel');
|
const validateModel = require('./validateModel');
|
||||||
const moderateText = require('./moderateText');
|
const moderateText = require('./moderateText');
|
||||||
|
|
@ -40,8 +41,7 @@ module.exports = {
|
||||||
moderateText,
|
moderateText,
|
||||||
validateModel,
|
validateModel,
|
||||||
requireJwtAuth,
|
requireJwtAuth,
|
||||||
requireAdminAuth,
|
requireAdmin,
|
||||||
requireAdminJwtAuth,
|
|
||||||
checkInviteUser,
|
checkInviteUser,
|
||||||
requireLdapAuth,
|
requireLdapAuth,
|
||||||
requireLocalAuth,
|
requireLocalAuth,
|
||||||
|
|
|
||||||
22
api/server/middleware/requireAdmin.js
Normal file
22
api/server/middleware/requireAdmin.js
Normal file
|
|
@ -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;
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -1,25 +1,40 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { adminVerifyController } = require('~/server/controllers/auth/AdminVerifyController');
|
const { loginController } = require('~/server/controllers/auth/LoginController');
|
||||||
const { adminLoginController } = require('~/server/controllers/auth/AdminLoginController');
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
const { createSetBalanceConfig } = require('@librechat/api');
|
||||||
const middleware = require('~/server/middleware');
|
const middleware = require('~/server/middleware');
|
||||||
|
const { Balance } = require('~/db/models');
|
||||||
|
|
||||||
|
const setBalanceConfig = createSetBalanceConfig({
|
||||||
|
getAppConfig,
|
||||||
|
Balance,
|
||||||
|
});
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Admin local authentication route
|
// Admin local authentication route - reuses main login controller
|
||||||
router.post(
|
router.post(
|
||||||
'/login/local',
|
'/login/local',
|
||||||
middleware.logHeaders,
|
middleware.logHeaders,
|
||||||
middleware.loginLimiter,
|
middleware.loginLimiter,
|
||||||
middleware.checkBan,
|
middleware.checkBan,
|
||||||
middleware.requireAdminAuth, // Uses local auth strategy + admin role validation
|
middleware.requireLocalAuth, // Standard local auth
|
||||||
adminLoginController,
|
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(
|
router.get(
|
||||||
'/verify',
|
'/verify',
|
||||||
middleware.requireAdminJwtAuth, // Validates JWT + admin role
|
middleware.requireJwtAuth, // Standard JWT auth
|
||||||
adminVerifyController,
|
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
|
// TODO: Future OAuth/OpenID routes will be added here
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue