WIP: first pass, OpenID Proxy Auth

This commit is contained in:
Danny Avila 2025-09-13 13:53:06 -04:00
parent e90fd1df15
commit f6925f906b
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
6 changed files with 317 additions and 206 deletions

View file

@ -0,0 +1,50 @@
const { isEnabled } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas');
const { syncUserEntraGroupMemberships } = require('~/server/services/PermissionService');
const { setAuthTokens, setOpenIDAuthTokens } = require('~/server/services/AuthService');
const { checkBan } = require('~/server/middleware');
const domains = {
client: process.env.DOMAIN_CLIENT,
server: process.env.DOMAIN_SERVER,
};
function createOAuthHandler(redirectUri = domains.client) {
/**
* A handler to process OAuth authentication results.
* @type {Function}
* @param {ServerRequest} req - Express request object.
* @param {ServerResponse} res - Express response object.
* @param {NextFunction} next - Express next middleware function.
*/
return async (req, res, next) => {
try {
if (res.headersSent) {
return;
}
await checkBan(req, res);
if (req.banned) {
return;
}
if (
req.user &&
req.user.provider == 'openid' &&
isEnabled(process.env.OPENID_REUSE_TOKENS) === true
) {
await syncUserEntraGroupMemberships(req.user, req.user.tokenset.access_token);
setOpenIDAuthTokens(req.user.tokenset, res, req.user._id.toString());
} else {
await setAuthTokens(req.user._id, res);
}
res.redirect(redirectUri);
} catch (err) {
logger.error('Error in setting authentication tokens:', err);
next(err);
}
};
}
module.exports = {
createOAuthHandler,
};

View file

@ -1,7 +1,11 @@
const express = require('express');
const { loginController } = require('~/server/controllers/auth/LoginController');
const { getAppConfig } = require('~/server/services/Config');
const passport = require('passport');
const { randomState } = require('openid-client');
const { createSetBalanceConfig } = require('@librechat/api');
const { loginController } = require('~/server/controllers/auth/LoginController');
const { createOAuthHandler } = require('~/server/controllers/auth/oauth');
const { getAppConfig } = require('~/server/services/Config');
const { getOpenIdConfig } = require('~/strategies');
const middleware = require('~/server/middleware');
const { Balance } = require('~/db/models');
@ -12,33 +16,51 @@ const setBalanceConfig = createSetBalanceConfig({
const router = express.Router();
// Admin local authentication route - reuses main login controller
router.post(
'/login/local',
middleware.logHeaders,
middleware.loginLimiter,
middleware.checkBan,
middleware.requireLocalAuth, // Standard local auth
middleware.requireAdmin, // Then check if user is admin
middleware.requireLocalAuth,
middleware.requireAdmin,
setBalanceConfig,
loginController, // Reuse existing login controller
loginController,
);
// Admin token verification endpoint - simple JWT verify + admin check
router.get('/verify', middleware.requireJwtAuth, middleware.requireAdmin, (req, res) => {
const { password: _p, totpSecret: _t, __v, ...user } = req.user;
user.id = user._id.toString();
res.status(200).json({ user });
});
router.get('/oauth/openid/check', (req, res) => {
const openidConfig = getOpenIdConfig();
if (!openidConfig) {
return res.status(404).json({ message: 'OpenID configuration not found' });
}
res.status(200).json({ message: 'OpenID check successful' });
});
router.get('/oauth/openid', (req, res, next) => {
return passport.authenticate('openidAdmin', {
session: false,
state: randomState(),
})(req, res, next);
});
router.get(
'/verify',
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 });
},
'/oauth/openid/callback',
passport.authenticate('openidAdmin', {
failureRedirect: `${process.env.DOMAIN_CLIENT}/oauth/error`,
failureMessage: true,
session: false,
}),
middleware.requireAdmin,
setBalanceConfig,
middleware.checkDomainAllowed,
createOAuthHandler(
(process.env.ADMIN_PANEL_URL || 'http://localhost:3000') + '/auth/openid/callback',
),
);
// TODO: Future OAuth/OpenID routes will be added here
// router.get('/auth/openid', ...);
// router.get('/auth/openid/callback', ...);
module.exports = router;

View file

@ -4,10 +4,9 @@ const passport = require('passport');
const { randomState } = require('openid-client');
const { logger } = require('@librechat/data-schemas');
const { ErrorTypes } = require('librechat-data-provider');
const { isEnabled, createSetBalanceConfig } = require('@librechat/api');
const { checkDomainAllowed, loginLimiter, logHeaders, checkBan } = require('~/server/middleware');
const { syncUserEntraGroupMemberships } = require('~/server/services/PermissionService');
const { setAuthTokens, setOpenIDAuthTokens } = require('~/server/services/AuthService');
const { createSetBalanceConfig } = require('@librechat/api');
const { checkDomainAllowed, loginLimiter, logHeaders } = require('~/server/middleware');
const { createOAuthHandler } = require('~/server/controllers/auth/oauth');
const { getAppConfig } = require('~/server/services/Config');
const { Balance } = require('~/db/models');
@ -26,32 +25,7 @@ const domains = {
router.use(logHeaders);
router.use(loginLimiter);
const oauthHandler = async (req, res, next) => {
try {
if (res.headersSent) {
return;
}
await checkBan(req, res);
if (req.banned) {
return;
}
if (
req.user &&
req.user.provider == 'openid' &&
isEnabled(process.env.OPENID_REUSE_TOKENS) === true
) {
await syncUserEntraGroupMemberships(req.user, req.user.tokenset.access_token);
setOpenIDAuthTokens(req.user.tokenset, res, req.user._id.toString());
} else {
await setAuthTokens(req.user._id, res);
}
res.redirect(domains.client);
} catch (err) {
logger.error('Error in setting authentication tokens:', err);
next(err);
}
};
const oauthHandler = createOAuthHandler();
router.get('/error', (req, res) => {
/** A single error message is pushed by passport when authentication fails. */