2025-06-09 11:27:23 -04:00
|
|
|
const undici = require('undici');
|
2024-05-17 13:03:31 -05:00
|
|
|
const fetch = require('node-fetch');
|
2023-12-14 07:49:27 -05:00
|
|
|
const passport = require('passport');
|
2025-05-30 22:18:13 -04:00
|
|
|
const client = require('openid-client');
|
2024-04-02 03:08:17 -04:00
|
|
|
const jwtDecode = require('jsonwebtoken/decode');
|
2025-05-30 22:18:13 -04:00
|
|
|
const { CacheKeys } = require('librechat-data-provider');
|
2024-06-15 15:41:34 +02:00
|
|
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
2025-05-30 22:18:13 -04:00
|
|
|
const { hashToken, logger } = require('@librechat/data-schemas');
|
2025-05-22 14:19:24 +02:00
|
|
|
const { Strategy: OpenIDStrategy } = require('openid-client/passport');
|
2025-06-09 11:27:23 -04:00
|
|
|
const { isEnabled, safeStringify, logHeaders } = require('@librechat/api');
|
2024-04-19 09:12:55 -04:00
|
|
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
2025-05-30 22:18:13 -04:00
|
|
|
const { findUser, createUser, updateUser } = require('~/models');
|
|
|
|
const { getBalanceConfig } = require('~/server/services/Config');
|
2025-05-22 14:19:24 +02:00
|
|
|
const getLogStores = require('~/cache/getLogStores');
|
2023-06-24 21:45:52 -05:00
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
/**
|
|
|
|
* @typedef {import('openid-client').ClientMetadata} ClientMetadata
|
|
|
|
* @typedef {import('openid-client').Configuration} Configuration
|
|
|
|
**/
|
|
|
|
|
2025-06-09 11:27:23 -04:00
|
|
|
/**
|
|
|
|
* @param {string} url
|
|
|
|
* @param {client.CustomFetchOptions} options
|
|
|
|
*/
|
|
|
|
async function customFetch(url, options) {
|
|
|
|
const urlStr = url.toString();
|
|
|
|
logger.debug(`[openidStrategy] Request to: ${urlStr}`);
|
|
|
|
const debugOpenId = isEnabled(process.env.DEBUG_OPENID_REQUESTS);
|
|
|
|
if (debugOpenId) {
|
|
|
|
logger.debug(`[openidStrategy] Request method: ${options.method || 'GET'}`);
|
|
|
|
logger.debug(`[openidStrategy] Request headers: ${logHeaders(options.headers)}`);
|
|
|
|
if (options.body) {
|
|
|
|
let bodyForLogging = '';
|
|
|
|
if (options.body instanceof URLSearchParams) {
|
|
|
|
bodyForLogging = options.body.toString();
|
|
|
|
} else if (typeof options.body === 'string') {
|
|
|
|
bodyForLogging = options.body;
|
|
|
|
} else {
|
|
|
|
bodyForLogging = safeStringify(options.body);
|
|
|
|
}
|
|
|
|
logger.debug(`[openidStrategy] Request body: ${bodyForLogging}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
/** @type {undici.RequestInit} */
|
|
|
|
let fetchOptions = options;
|
|
|
|
if (process.env.PROXY) {
|
|
|
|
logger.info(`[openidStrategy] proxy agent configured: ${process.env.PROXY}`);
|
|
|
|
fetchOptions = {
|
|
|
|
...options,
|
2025-07-01 22:30:06 +02:00
|
|
|
dispatcher: new undici.ProxyAgent(process.env.PROXY),
|
2025-06-09 11:27:23 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const response = await undici.fetch(url, fetchOptions);
|
|
|
|
|
|
|
|
if (debugOpenId) {
|
|
|
|
logger.debug(`[openidStrategy] Response status: ${response.status} ${response.statusText}`);
|
|
|
|
logger.debug(`[openidStrategy] Response headers: ${logHeaders(response.headers)}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (response.status === 200 && response.headers.has('www-authenticate')) {
|
|
|
|
const wwwAuth = response.headers.get('www-authenticate');
|
|
|
|
logger.warn(`[openidStrategy] Non-standard WWW-Authenticate header found in successful response (200 OK): ${wwwAuth}.
|
|
|
|
This violates RFC 7235 and may cause issues with strict OAuth clients. Removing header for compatibility.`);
|
|
|
|
|
|
|
|
/** Cloned response without the WWW-Authenticate header */
|
|
|
|
const responseBody = await response.arrayBuffer();
|
|
|
|
const newHeaders = new Headers();
|
|
|
|
for (const [key, value] of response.headers.entries()) {
|
|
|
|
if (key.toLowerCase() !== 'www-authenticate') {
|
|
|
|
newHeaders.append(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Response(responseBody, {
|
|
|
|
status: response.status,
|
|
|
|
statusText: response.statusText,
|
|
|
|
headers: newHeaders,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
} catch (error) {
|
|
|
|
logger.error(`[openidStrategy] Fetch error: ${error.message}`);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
/** @typedef {Configuration | null} */
|
|
|
|
let openidConfig = null;
|
|
|
|
|
|
|
|
//overload currenturl function because of express version 4 buggy req.host doesn't include port
|
|
|
|
//More info https://github.com/panva/openid-client/pull/713
|
|
|
|
|
|
|
|
class CustomOpenIDStrategy extends OpenIDStrategy {
|
|
|
|
currentUrl(req) {
|
|
|
|
const hostAndProtocol = process.env.DOMAIN_SERVER;
|
|
|
|
return new URL(`${hostAndProtocol}${req.originalUrl ?? req.url}`);
|
|
|
|
}
|
2025-05-25 23:40:37 -04:00
|
|
|
authorizationRequestParams(req, options) {
|
|
|
|
const params = super.authorizationRequestParams(req, options);
|
|
|
|
if (options?.state && !params.has('state')) {
|
|
|
|
params.set('state', options.state);
|
|
|
|
}
|
2025-08-05 02:49:36 +08:00
|
|
|
|
|
|
|
if (process.env.OPENID_AUDIENCE) {
|
|
|
|
params.set('audience', process.env.OPENID_AUDIENCE);
|
|
|
|
logger.debug(
|
|
|
|
`[openidStrategy] Adding audience to authorization request: ${process.env.OPENID_AUDIENCE}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-05-25 23:40:37 -04:00
|
|
|
return params;
|
|
|
|
}
|
2023-06-24 21:45:52 -05:00
|
|
|
}
|
2024-10-27 11:41:48 -04:00
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
/**
|
|
|
|
* Exchange the access token for a new access token using the on-behalf-of flow if required.
|
|
|
|
* @param {Configuration} config
|
|
|
|
* @param {string} accessToken access token to be exchanged if necessary
|
|
|
|
* @param {string} sub - The subject identifier of the user. usually found as "sub" in the claims of the token
|
|
|
|
* @param {boolean} fromCache - Indicates whether to use cached tokens.
|
|
|
|
* @returns {Promise<string>} The new access token if exchanged, otherwise the original access token.
|
|
|
|
*/
|
|
|
|
const exchangeAccessTokenIfNeeded = async (config, accessToken, sub, fromCache = false) => {
|
|
|
|
const tokensCache = getLogStores(CacheKeys.OPENID_EXCHANGED_TOKENS);
|
2025-06-26 19:10:21 -04:00
|
|
|
const onBehalfFlowRequired = isEnabled(process.env.OPENID_ON_BEHALF_FLOW_FOR_USERINFO_REQUIRED);
|
2025-05-22 14:19:24 +02:00
|
|
|
if (onBehalfFlowRequired) {
|
|
|
|
if (fromCache) {
|
|
|
|
const cachedToken = await tokensCache.get(sub);
|
|
|
|
if (cachedToken) {
|
|
|
|
return cachedToken.access_token;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const grantResponse = await client.genericGrantRequest(
|
|
|
|
config,
|
|
|
|
'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
|
|
{
|
2025-06-26 19:10:21 -04:00
|
|
|
scope: process.env.OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE || 'user.read',
|
2025-05-22 14:19:24 +02:00
|
|
|
assertion: accessToken,
|
|
|
|
requested_token_use: 'on_behalf_of',
|
|
|
|
},
|
|
|
|
);
|
|
|
|
await tokensCache.set(
|
|
|
|
sub,
|
|
|
|
{
|
|
|
|
access_token: grantResponse.access_token,
|
|
|
|
},
|
|
|
|
grantResponse.expires_in * 1000,
|
|
|
|
);
|
|
|
|
return grantResponse.access_token;
|
|
|
|
}
|
|
|
|
return accessToken;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get user info from openid provider
|
|
|
|
* @param {Configuration} config
|
|
|
|
* @param {string} accessToken access token
|
|
|
|
* @param {string} sub - The subject identifier of the user. usually found as "sub" in the claims of the token
|
|
|
|
* @returns {Promise<Object|null>}
|
|
|
|
*/
|
|
|
|
const getUserInfo = async (config, accessToken, sub) => {
|
|
|
|
try {
|
|
|
|
const exchangedAccessToken = await exchangeAccessTokenIfNeeded(config, accessToken, sub);
|
|
|
|
return await client.fetchUserInfo(config, exchangedAccessToken, sub);
|
|
|
|
} catch (error) {
|
|
|
|
logger.warn(`[openidStrategy] getUserInfo: Error fetching user info: ${error}`);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-04-19 09:12:55 -04:00
|
|
|
/**
|
|
|
|
* Downloads an image from a URL using an access token.
|
|
|
|
* @param {string} url
|
2025-05-22 14:19:24 +02:00
|
|
|
* @param {Configuration} config
|
|
|
|
* @param {string} accessToken access token
|
|
|
|
* @param {string} sub - The subject identifier of the user. usually found as "sub" in the claims of the token
|
|
|
|
* @returns {Promise<Buffer | string>} The image buffer or an empty string if the download fails.
|
2024-04-19 09:12:55 -04:00
|
|
|
*/
|
2025-05-22 14:19:24 +02:00
|
|
|
const downloadImage = async (url, config, accessToken, sub) => {
|
|
|
|
const exchangedAccessToken = await exchangeAccessTokenIfNeeded(config, accessToken, sub, true);
|
2024-04-19 09:12:55 -04:00
|
|
|
if (!url) {
|
|
|
|
return '';
|
|
|
|
}
|
2023-06-24 21:45:52 -05:00
|
|
|
|
|
|
|
try {
|
2024-07-05 17:23:06 +03:00
|
|
|
const options = {
|
2024-04-19 09:12:55 -04:00
|
|
|
method: 'GET',
|
2023-06-24 21:45:52 -05:00
|
|
|
headers: {
|
2025-05-22 14:19:24 +02:00
|
|
|
Authorization: `Bearer ${exchangedAccessToken}`,
|
2023-06-24 21:45:52 -05:00
|
|
|
},
|
2024-07-05 17:23:06 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
if (process.env.PROXY) {
|
|
|
|
options.agent = new HttpsProxyAgent(process.env.PROXY);
|
|
|
|
}
|
|
|
|
|
|
|
|
const response = await fetch(url, options);
|
2023-07-14 09:36:49 -04:00
|
|
|
|
2024-04-19 09:12:55 -04:00
|
|
|
if (response.ok) {
|
|
|
|
const buffer = await response.buffer();
|
|
|
|
return buffer;
|
|
|
|
} else {
|
|
|
|
throw new Error(`${response.statusText} (HTTP ${response.status})`);
|
|
|
|
}
|
2023-06-24 21:45:52 -05:00
|
|
|
} catch (error) {
|
2023-12-14 07:49:27 -05:00
|
|
|
logger.error(
|
|
|
|
`[openidStrategy] downloadImage: Error downloading image at URL "${url}": ${error}`,
|
|
|
|
);
|
2023-06-24 21:45:52 -05:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-10-27 11:41:48 -04:00
|
|
|
/**
|
|
|
|
* Determines the full name of a user based on OpenID userinfo and environment configuration.
|
|
|
|
*
|
|
|
|
* @param {Object} userinfo - The user information object from OpenID Connect
|
|
|
|
* @param {string} [userinfo.given_name] - The user's first name
|
|
|
|
* @param {string} [userinfo.family_name] - The user's last name
|
|
|
|
* @param {string} [userinfo.username] - The user's username
|
|
|
|
* @param {string} [userinfo.email] - The user's email address
|
|
|
|
* @returns {string} The determined full name of the user
|
|
|
|
*/
|
|
|
|
function getFullName(userinfo) {
|
|
|
|
if (process.env.OPENID_NAME_CLAIM) {
|
|
|
|
return userinfo[process.env.OPENID_NAME_CLAIM];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (userinfo.given_name && userinfo.family_name) {
|
|
|
|
return `${userinfo.given_name} ${userinfo.family_name}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (userinfo.given_name) {
|
|
|
|
return userinfo.given_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (userinfo.family_name) {
|
|
|
|
return userinfo.family_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
return userinfo.username || userinfo.email;
|
|
|
|
}
|
|
|
|
|
2024-04-12 12:39:11 -04:00
|
|
|
/**
|
|
|
|
* Converts an input into a string suitable for a username.
|
|
|
|
* If the input is a string, it will be returned as is.
|
|
|
|
* If the input is an array, elements will be joined with underscores.
|
|
|
|
* In case of undefined or other falsy values, a default value will be returned.
|
|
|
|
*
|
|
|
|
* @param {string | string[] | undefined} input - The input value to be converted into a username.
|
|
|
|
* @param {string} [defaultValue=''] - The default value to return if the input is falsy.
|
|
|
|
* @returns {string} The processed input as a string suitable for a username.
|
|
|
|
*/
|
|
|
|
function convertToUsername(input, defaultValue = '') {
|
|
|
|
if (typeof input === 'string') {
|
|
|
|
return input;
|
|
|
|
} else if (Array.isArray(input)) {
|
|
|
|
return input.join('_');
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
/**
|
|
|
|
* Sets up the OpenID strategy for authentication.
|
|
|
|
* This function configures the OpenID client, handles proxy settings,
|
|
|
|
* and defines the OpenID strategy for Passport.js.
|
|
|
|
*
|
|
|
|
* @async
|
|
|
|
* @function setupOpenId
|
|
|
|
* @returns {Promise<Configuration | null>} A promise that resolves when the OpenID strategy is set up and returns the openid client config object.
|
|
|
|
* @throws {Error} If an error occurs during the setup process.
|
|
|
|
*/
|
2023-07-22 07:29:17 -07:00
|
|
|
async function setupOpenId() {
|
|
|
|
try {
|
2025-05-22 14:19:24 +02:00
|
|
|
/** @type {ClientMetadata} */
|
2025-01-21 21:49:27 -05:00
|
|
|
const clientMetadata = {
|
2023-06-24 21:45:52 -05:00
|
|
|
client_id: process.env.OPENID_CLIENT_ID,
|
|
|
|
client_secret: process.env.OPENID_CLIENT_SECRET,
|
2025-01-21 21:49:27 -05:00
|
|
|
};
|
2025-05-22 14:19:24 +02:00
|
|
|
|
|
|
|
/** @type {Configuration} */
|
|
|
|
openidConfig = await client.discovery(
|
|
|
|
new URL(process.env.OPENID_ISSUER),
|
|
|
|
process.env.OPENID_CLIENT_ID,
|
|
|
|
clientMetadata,
|
2025-06-09 11:27:23 -04:00
|
|
|
undefined,
|
|
|
|
{
|
|
|
|
[client.customFetch]: customFetch,
|
|
|
|
},
|
2025-05-22 14:19:24 +02:00
|
|
|
);
|
2025-06-09 11:27:23 -04:00
|
|
|
|
2024-04-02 03:08:17 -04:00
|
|
|
const requiredRole = process.env.OPENID_REQUIRED_ROLE;
|
|
|
|
const requiredRoleParameterPath = process.env.OPENID_REQUIRED_ROLE_PARAMETER_PATH;
|
|
|
|
const requiredRoleTokenKind = process.env.OPENID_REQUIRED_ROLE_TOKEN_KIND;
|
2025-05-22 14:19:24 +02:00
|
|
|
const usePKCE = isEnabled(process.env.OPENID_USE_PKCE);
|
|
|
|
const openidLogin = new CustomOpenIDStrategy(
|
2023-06-24 21:45:52 -05:00
|
|
|
{
|
2025-05-22 14:19:24 +02:00
|
|
|
config: openidConfig,
|
|
|
|
scope: process.env.OPENID_SCOPE,
|
|
|
|
callbackURL: process.env.DOMAIN_SERVER + process.env.OPENID_CALLBACK_URL,
|
|
|
|
usePKCE,
|
2023-06-24 21:45:52 -05:00
|
|
|
},
|
2025-05-22 14:19:24 +02:00
|
|
|
async (tokenset, done) => {
|
2023-06-24 21:45:52 -05:00
|
|
|
try {
|
2025-05-22 14:19:24 +02:00
|
|
|
const claims = tokenset.claims();
|
|
|
|
let user = await findUser({ openidId: claims.sub });
|
2024-05-30 10:48:03 -04:00
|
|
|
logger.info(
|
2025-05-22 14:19:24 +02:00
|
|
|
`[openidStrategy] user ${user ? 'found' : 'not found'} with openidId: ${claims.sub}`,
|
2024-05-30 10:48:03 -04:00
|
|
|
);
|
2023-06-24 21:45:52 -05:00
|
|
|
|
|
|
|
if (!user) {
|
2025-05-22 14:19:24 +02:00
|
|
|
user = await findUser({ email: claims.email });
|
2024-05-30 10:48:03 -04:00
|
|
|
logger.info(
|
|
|
|
`[openidStrategy] user ${user ? 'found' : 'not found'} with email: ${
|
2025-05-22 14:19:24 +02:00
|
|
|
claims.email
|
|
|
|
} for openidId: ${claims.sub}`,
|
2024-05-30 10:48:03 -04:00
|
|
|
);
|
2023-06-24 21:45:52 -05:00
|
|
|
}
|
2025-05-22 14:19:24 +02:00
|
|
|
const userinfo = {
|
|
|
|
...claims,
|
|
|
|
...(await getUserInfo(openidConfig, tokenset.access_token, claims.sub)),
|
|
|
|
};
|
2024-10-27 11:41:48 -04:00
|
|
|
const fullName = getFullName(userinfo);
|
2023-07-14 09:36:49 -04:00
|
|
|
|
2024-04-02 03:08:17 -04:00
|
|
|
if (requiredRole) {
|
|
|
|
let decodedToken = '';
|
|
|
|
if (requiredRoleTokenKind === 'access') {
|
|
|
|
decodedToken = jwtDecode(tokenset.access_token);
|
|
|
|
} else if (requiredRoleTokenKind === 'id') {
|
|
|
|
decodedToken = jwtDecode(tokenset.id_token);
|
|
|
|
}
|
|
|
|
const pathParts = requiredRoleParameterPath.split('.');
|
|
|
|
let found = true;
|
|
|
|
let roles = pathParts.reduce((o, key) => {
|
|
|
|
if (o === null || o === undefined || !(key in o)) {
|
|
|
|
found = false;
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return o[key];
|
|
|
|
}, decodedToken);
|
|
|
|
|
|
|
|
if (!found) {
|
2024-05-30 10:48:03 -04:00
|
|
|
logger.error(
|
|
|
|
`[openidStrategy] Key '${requiredRoleParameterPath}' not found in ${requiredRoleTokenKind} token!`,
|
2024-04-02 03:08:17 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!roles.includes(requiredRole)) {
|
|
|
|
return done(null, false, {
|
|
|
|
message: `You must have the "${requiredRole}" role to log in.`,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-27 11:41:48 -04:00
|
|
|
let username = '';
|
|
|
|
if (process.env.OPENID_USERNAME_CLAIM) {
|
|
|
|
username = userinfo[process.env.OPENID_USERNAME_CLAIM];
|
|
|
|
} else {
|
|
|
|
username = convertToUsername(
|
2025-07-30 14:43:42 -04:00
|
|
|
userinfo.preferred_username || userinfo.username || userinfo.email,
|
2024-10-27 11:41:48 -04:00
|
|
|
);
|
|
|
|
}
|
2024-04-12 12:39:11 -04:00
|
|
|
|
2023-06-24 21:45:52 -05:00
|
|
|
if (!user) {
|
2024-06-07 21:06:47 +02:00
|
|
|
user = {
|
2023-06-24 21:45:52 -05:00
|
|
|
provider: 'openid',
|
|
|
|
openidId: userinfo.sub,
|
2024-04-12 12:39:11 -04:00
|
|
|
username,
|
2023-06-24 21:45:52 -05:00
|
|
|
email: userinfo.email || '',
|
|
|
|
emailVerified: userinfo.email_verified || false,
|
2023-07-14 09:36:49 -04:00
|
|
|
name: fullName,
|
2024-06-07 21:06:47 +02:00
|
|
|
};
|
2025-05-30 22:18:13 -04:00
|
|
|
|
|
|
|
const balanceConfig = await getBalanceConfig();
|
|
|
|
|
|
|
|
user = await createUser(user, balanceConfig, true, true);
|
2023-06-24 21:45:52 -05:00
|
|
|
} else {
|
|
|
|
user.provider = 'openid';
|
|
|
|
user.openidId = userinfo.sub;
|
2024-04-12 12:39:11 -04:00
|
|
|
user.username = username;
|
2023-06-24 21:45:52 -05:00
|
|
|
user.name = fullName;
|
|
|
|
}
|
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
if (!!userinfo && userinfo.picture && !user.avatar?.includes('manual=true')) {
|
2024-04-19 09:12:55 -04:00
|
|
|
/** @type {string | undefined} */
|
2023-06-24 21:45:52 -05:00
|
|
|
const imageUrl = userinfo.picture;
|
|
|
|
|
|
|
|
let fileName;
|
|
|
|
if (crypto) {
|
2024-08-04 23:59:45 -04:00
|
|
|
fileName = (await hashToken(userinfo.sub)) + '.png';
|
2023-06-24 21:45:52 -05:00
|
|
|
} else {
|
|
|
|
fileName = userinfo.sub + '.png';
|
|
|
|
}
|
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
const imageBuffer = await downloadImage(
|
|
|
|
imageUrl,
|
|
|
|
openidConfig,
|
|
|
|
tokenset.access_token,
|
|
|
|
userinfo.sub,
|
|
|
|
);
|
2024-04-19 09:12:55 -04:00
|
|
|
if (imageBuffer) {
|
2024-06-07 21:06:47 +02:00
|
|
|
const { saveBuffer } = getStrategyFunctions(process.env.CDN_PROVIDER);
|
2024-04-19 09:12:55 -04:00
|
|
|
const imagePath = await saveBuffer({
|
|
|
|
fileName,
|
|
|
|
userId: user._id.toString(),
|
|
|
|
buffer: imageBuffer,
|
|
|
|
});
|
|
|
|
user.avatar = imagePath ?? '';
|
|
|
|
}
|
2023-06-24 21:45:52 -05:00
|
|
|
}
|
2023-07-14 09:36:49 -04:00
|
|
|
|
2024-06-07 21:06:47 +02:00
|
|
|
user = await updateUser(user._id, user);
|
2023-07-14 09:36:49 -04:00
|
|
|
|
2024-05-30 10:48:03 -04:00
|
|
|
logger.info(
|
2024-06-07 21:06:47 +02:00
|
|
|
`[openidStrategy] login success openidId: ${user.openidId} | email: ${user.email} | username: ${user.username} `,
|
2024-05-30 10:48:03 -04:00
|
|
|
{
|
|
|
|
user: {
|
|
|
|
openidId: user.openidId,
|
|
|
|
username: user.username,
|
|
|
|
email: user.email,
|
|
|
|
name: user.name,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
done(null, { ...user, tokenset });
|
2023-06-24 21:45:52 -05:00
|
|
|
} catch (err) {
|
2024-05-30 10:48:03 -04:00
|
|
|
logger.error('[openidStrategy] login failed', err);
|
2023-06-24 21:45:52 -05:00
|
|
|
done(err);
|
|
|
|
}
|
2023-07-14 09:36:49 -04:00
|
|
|
},
|
2023-06-24 21:45:52 -05:00
|
|
|
);
|
|
|
|
passport.use('openid', openidLogin);
|
2025-05-22 14:19:24 +02:00
|
|
|
return openidConfig;
|
2023-07-22 07:29:17 -07:00
|
|
|
} catch (err) {
|
2023-12-14 07:49:27 -05:00
|
|
|
logger.error('[openidStrategy]', err);
|
2025-05-22 14:19:24 +02:00
|
|
|
return null;
|
2023-07-22 07:29:17 -07:00
|
|
|
}
|
|
|
|
}
|
2025-05-22 14:19:24 +02:00
|
|
|
/**
|
|
|
|
* @function getOpenIdConfig
|
|
|
|
* @description Returns the OpenID client instance.
|
|
|
|
* @throws {Error} If the OpenID client is not initialized.
|
|
|
|
* @returns {Configuration}
|
|
|
|
*/
|
|
|
|
function getOpenIdConfig() {
|
|
|
|
if (!openidConfig) {
|
|
|
|
throw new Error('OpenID client is not initialized. Please call setupOpenId first.');
|
|
|
|
}
|
|
|
|
return openidConfig;
|
|
|
|
}
|
2023-07-22 07:29:17 -07:00
|
|
|
|
2025-05-22 14:19:24 +02:00
|
|
|
module.exports = {
|
|
|
|
setupOpenId,
|
|
|
|
getOpenIdConfig,
|
|
|
|
};
|