📧 feat: email verification (#2344)

* 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>
This commit is contained in:
Marco Beretta 2024-06-07 21:06:47 +02:00 committed by GitHub
parent b7fef6958b
commit ee673d682e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 1863 additions and 1117 deletions

View file

@ -1,14 +1,14 @@
const { FileSources } = require('librechat-data-provider');
const { createUser, updateUser, getUserById } = require('~/models/userMethods');
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
const User = require('~/models/User');
/**
* Updates the avatar URL of an existing user. If the user's avatar URL does not include the query parameter
* '?manual=true', it updates the user's avatar with the provided URL. For local file storage, it directly updates
* the avatar URL, while for other storage types, it processes the avatar URL using the specified file strategy.
*
* @param {User} oldUser - The existing user object that needs to be updated.
* @param {MongoUser} oldUser - The existing user object that needs to be updated.
* @param {string} avatarUrl - The new avatar URL to be set for the user.
*
* @returns {Promise<void>}
@ -20,9 +20,9 @@ const handleExistingUser = async (oldUser, avatarUrl) => {
const fileStrategy = process.env.CDN_PROVIDER;
const isLocal = fileStrategy === FileSources.local;
let updatedAvatar = false;
if (isLocal && (oldUser.avatar === null || !oldUser.avatar.includes('?manual=true'))) {
oldUser.avatar = avatarUrl;
await oldUser.save();
updatedAvatar = avatarUrl;
} else if (!isLocal && (oldUser.avatar === null || !oldUser.avatar.includes('?manual=true'))) {
const userId = oldUser._id;
const resizedBuffer = await resizeAvatar({
@ -30,8 +30,11 @@ const handleExistingUser = async (oldUser, avatarUrl) => {
input: avatarUrl,
});
const { processAvatar } = getStrategyFunctions(fileStrategy);
oldUser.avatar = await processAvatar({ buffer: resizedBuffer, userId });
await oldUser.save();
updatedAvatar = await processAvatar({ buffer: resizedBuffer, userId });
}
if (updatedAvatar) {
await updateUser(oldUser._id, { avatar: updatedAvatar });
}
};
@ -55,7 +58,7 @@ const handleExistingUser = async (oldUser, avatarUrl) => {
*
* @throws {Error} Throws an error if there's an issue creating or saving the new user object.
*/
const createNewUser = async ({
const createSocialUser = async ({
email,
avatarUrl,
provider,
@ -75,27 +78,24 @@ const createNewUser = async ({
emailVerified,
};
// TODO: remove direct access of User model
const newUser = await new User(update).save();
const newUserId = await createUser(update);
const fileStrategy = process.env.CDN_PROVIDER;
const isLocal = fileStrategy === FileSources.local;
if (!isLocal) {
const userId = newUser._id;
const resizedBuffer = await resizeAvatar({
userId,
userId: newUserId,
input: avatarUrl,
});
const { processAvatar } = getStrategyFunctions(fileStrategy);
newUser.avatar = await processAvatar({ buffer: resizedBuffer, userId });
await newUser.save();
const avatar = await processAvatar({ buffer: resizedBuffer, userId: newUserId });
await updateUser(newUserId, { avatar });
}
return newUser;
return await getUserById(newUserId);
};
module.exports = {
handleExistingUser,
createNewUser,
createSocialUser,
};