mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00

* feat: verification email * chore: email verification invalid; localize: update * fix: redirect to login when signup: fix: save emailVerified correctly * docs: update ALLOW_UNVERIFIED_EMAIL_LOGIN; fix: don't accept login only when ALLOW_UNVERIFIED_EMAIL_LOGIN = true * fix: user needs to be authenticated * style: update * fix: registration success message and redirect logic * refactor: use `isEnabled` in ALLOW_UNVERIFIED_EMAIL_LOGIN * refactor: move checkEmailConfig to server/utils * refactor: use req as param for verifyEmail function * chore: jsdoc * chore: remove console log * refactor: rename `createNewUser` to `createSocialUser` * refactor: update typing and add expiresAt field to userSchema * refactor: begin use of user methods over direct model access for User * refactor: initial email verification rewrite * chore: typing * refactor: registration flow rewrite * chore: remove help center text * refactor: update getUser to getUserById and add findUser methods. general fixes from recent changes * refactor: Update updateUser method to remove expiresAt field and use $set and $unset operations, createUser now returns Id only * refactor: Update openidStrategy to use optional chaining for avatar check, move saveBuffer init to buffer condition * refactor: logout on deleteUser mutatation * refactor: Update openidStrategy login success message format * refactor: Add emailVerified field to Discord and Facebook profile details * refactor: move limiters to separate middleware dir * refactor: Add limiters for email verification and password reset * refactor: Remove getUserController and update routes and controllers accordingly * refactor: Update getUserById method to exclude password and version fields * refactor: move verification to user route, add resend verification option * refactor: Improve email verification process and resend option * refactor: remove more direct model access of User and remove unused code * refactor: replace user authentication methods and token generation * fix: add user.id to jwt user * refactor: Update AuthContext to include setError function, add resend link to Login Form, make registration redirect shorter * fix(updateUserPluginsService): ensure userPlugins variable is defined * refactor: Delete all shared links for a specific user * fix: remove use of direct User.save() in handleExistingUser * fix(importLibreChatConvo): handle missing createdAt field in messages --------- Co-authored-by: Danny Avila <danny@librechat.ai>
170 lines
5.2 KiB
JavaScript
170 lines
5.2 KiB
JavaScript
const bcrypt = require('bcryptjs');
|
|
const signPayload = require('~/server/services/signPayload');
|
|
const User = require('./User');
|
|
|
|
/**
|
|
* Retrieve a user by ID and convert the found user document to a plain object.
|
|
*
|
|
* @param {string} userId - The ID of the user to find and return as a plain object.
|
|
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
|
* @returns {Promise<MongoUser>} A plain object representing the user document, or `null` if no user is found.
|
|
*/
|
|
const getUserById = async function (userId, fieldsToSelect = null) {
|
|
const query = User.findById(userId);
|
|
|
|
if (fieldsToSelect) {
|
|
query.select(fieldsToSelect);
|
|
}
|
|
|
|
return await query.lean();
|
|
};
|
|
|
|
/**
|
|
* Search for a single user based on partial data and return matching user document as plain object.
|
|
* @param {Partial<MongoUser>} searchCriteria - The partial data to use for searching the user.
|
|
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
|
* @returns {Promise<MongoUser>} A plain object representing the user document, or `null` if no user is found.
|
|
*/
|
|
const findUser = async function (searchCriteria, fieldsToSelect = null) {
|
|
const query = User.findOne(searchCriteria);
|
|
if (fieldsToSelect) {
|
|
query.select(fieldsToSelect);
|
|
}
|
|
|
|
return await query.lean();
|
|
};
|
|
|
|
/**
|
|
* Update a user with new data without overwriting existing properties.
|
|
*
|
|
* @param {string} userId - The ID of the user to update.
|
|
* @param {Object} updateData - An object containing the properties to update.
|
|
* @returns {Promise<MongoUser>} The updated user document as a plain object, or `null` if no user is found.
|
|
*/
|
|
const updateUser = async function (userId, updateData) {
|
|
const updateOperation = {
|
|
$set: updateData,
|
|
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
|
};
|
|
return await User.findByIdAndUpdate(userId, updateOperation, {
|
|
new: true,
|
|
runValidators: true,
|
|
}).lean();
|
|
};
|
|
|
|
/**
|
|
* Creates a new user, optionally with a TTL of 1 week.
|
|
* @param {MongoUser} data - The user data to be created, must contain user_id.
|
|
* @param {boolean} [disableTTL=true] - Whether to disable the TTL. Defaults to `true`.
|
|
* @returns {Promise<string>} A promise that resolves to the created user document ID.
|
|
* @throws {Error} If a user with the same user_id already exists.
|
|
*/
|
|
const createUser = async (data, disableTTL = true) => {
|
|
const userData = {
|
|
...data,
|
|
expiresAt: new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
|
};
|
|
|
|
if (disableTTL) {
|
|
delete userData.expiresAt;
|
|
}
|
|
|
|
try {
|
|
const result = await User.collection.insertOne(userData);
|
|
return result.insertedId;
|
|
} catch (error) {
|
|
if (error.code === 11000) {
|
|
// Duplicate key error code
|
|
throw new Error(`User with \`_id\` ${data._id} already exists.`);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Count the number of user documents in the collection based on the provided filter.
|
|
*
|
|
* @param {Object} [filter={}] - The filter to apply when counting the documents.
|
|
* @returns {Promise<number>} The count of documents that match the filter.
|
|
*/
|
|
const countUsers = async function (filter = {}) {
|
|
return await User.countDocuments(filter);
|
|
};
|
|
|
|
/**
|
|
* Delete a user by their unique ID.
|
|
*
|
|
* @param {string} userId - The ID of the user to delete.
|
|
* @returns {Promise<{ deletedCount: number }>} An object indicating the number of deleted documents.
|
|
*/
|
|
const deleteUserById = async function (userId) {
|
|
try {
|
|
const result = await User.deleteOne({ _id: userId });
|
|
if (result.deletedCount === 0) {
|
|
return { deletedCount: 0, message: 'No user found with that ID.' };
|
|
}
|
|
return { deletedCount: result.deletedCount, message: 'User was deleted successfully.' };
|
|
} catch (error) {
|
|
throw new Error('Error deleting user: ' + error.message);
|
|
}
|
|
};
|
|
|
|
const { SESSION_EXPIRY } = process.env ?? {};
|
|
const expires = eval(SESSION_EXPIRY) ?? 1000 * 60 * 15;
|
|
|
|
/**
|
|
* Generates a JWT token for a given user.
|
|
*
|
|
* @param {MongoUser} user - ID of the user for whom the token is being generated.
|
|
* @returns {Promise<string>} A promise that resolves to a JWT token.
|
|
*/
|
|
const generateToken = async (user) => {
|
|
if (!user) {
|
|
throw new Error('No user provided');
|
|
}
|
|
|
|
return await signPayload({
|
|
payload: {
|
|
id: user._id,
|
|
username: user.username,
|
|
provider: user.provider,
|
|
email: user.email,
|
|
},
|
|
secret: process.env.JWT_SECRET,
|
|
expirationTime: expires / 1000,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Compares the provided password with the user's password.
|
|
*
|
|
* @param {MongoUser} user - the user to compare password for.
|
|
* @param {string} candidatePassword - The password to test against the user's password.
|
|
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating if the password matches.
|
|
*/
|
|
const comparePassword = async (user, candidatePassword) => {
|
|
if (!user) {
|
|
throw new Error('No user provided');
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
bcrypt.compare(candidatePassword, user.password, (err, isMatch) => {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
resolve(isMatch);
|
|
});
|
|
});
|
|
};
|
|
|
|
module.exports = {
|
|
comparePassword,
|
|
deleteUserById,
|
|
generateToken,
|
|
getUserById,
|
|
countUsers,
|
|
createUser,
|
|
updateUser,
|
|
findUser,
|
|
};
|