mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🏷️ fix: Categorize Auth Tokens by Flow Type (#13556)
* fix: Scope auth token lifecycle * fix: Preserve legacy auth token lookup * fix: Scope verification token cleanup
This commit is contained in:
parent
5011be4d38
commit
3571dfcf22
5 changed files with 545 additions and 49 deletions
|
|
@ -45,9 +45,112 @@ const domains = {
|
|||
server: process.env.DOMAIN_SERVER,
|
||||
};
|
||||
|
||||
const AuthTokenTypes = Object.freeze({
|
||||
EMAIL_VERIFICATION: 'email_verification',
|
||||
PASSWORD_RESET: 'password_reset',
|
||||
});
|
||||
|
||||
const latestAuthTokenOptions = Object.freeze({ sort: { createdAt: -1 } });
|
||||
const genericVerificationMessage = 'Please check your email to verify your email address.';
|
||||
const OPENID_SESSION_ID_TOKEN_EXPIRY_BUFFER_SECONDS = 30;
|
||||
|
||||
const findPasswordResetToken = async (userId) => {
|
||||
const typedToken = await findToken(
|
||||
{
|
||||
userId,
|
||||
type: AuthTokenTypes.PASSWORD_RESET,
|
||||
},
|
||||
latestAuthTokenOptions,
|
||||
);
|
||||
|
||||
if (typedToken) {
|
||||
return typedToken;
|
||||
}
|
||||
|
||||
return await findToken(
|
||||
{
|
||||
userId,
|
||||
email: null,
|
||||
identifier: null,
|
||||
type: null,
|
||||
},
|
||||
latestAuthTokenOptions,
|
||||
);
|
||||
};
|
||||
|
||||
const findEmailVerificationToken = async (user) => {
|
||||
const typedToken = await findToken(
|
||||
{
|
||||
userId: user._id,
|
||||
email: user.email,
|
||||
type: AuthTokenTypes.EMAIL_VERIFICATION,
|
||||
},
|
||||
latestAuthTokenOptions,
|
||||
);
|
||||
|
||||
if (typedToken) {
|
||||
return typedToken;
|
||||
}
|
||||
|
||||
return await findToken(
|
||||
{
|
||||
userId: user._id,
|
||||
email: user.email,
|
||||
identifier: null,
|
||||
type: null,
|
||||
},
|
||||
latestAuthTokenOptions,
|
||||
);
|
||||
};
|
||||
|
||||
const deleteEmailVerificationTokens = (user) =>
|
||||
Promise.all([
|
||||
deleteTokens({
|
||||
userId: user._id,
|
||||
email: user.email,
|
||||
type: AuthTokenTypes.EMAIL_VERIFICATION,
|
||||
}),
|
||||
deleteTokens({
|
||||
userId: user._id,
|
||||
email: user.email,
|
||||
identifier: null,
|
||||
type: null,
|
||||
}),
|
||||
]);
|
||||
|
||||
const getEmailVerificationTokenDeleteQuery = (emailVerificationToken) => {
|
||||
if (!emailVerificationToken.identifier && !emailVerificationToken.type) {
|
||||
return {
|
||||
token: emailVerificationToken.token,
|
||||
userId: emailVerificationToken.userId,
|
||||
email: emailVerificationToken.email,
|
||||
identifier: null,
|
||||
type: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
token: emailVerificationToken.token,
|
||||
type: AuthTokenTypes.EMAIL_VERIFICATION,
|
||||
};
|
||||
};
|
||||
|
||||
const getPasswordResetTokenDeleteQuery = (passwordResetToken) => {
|
||||
if (!passwordResetToken.email && !passwordResetToken.type) {
|
||||
return {
|
||||
token: passwordResetToken.token,
|
||||
email: null,
|
||||
identifier: null,
|
||||
type: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
token: passwordResetToken.token,
|
||||
type: AuthTokenTypes.PASSWORD_RESET,
|
||||
};
|
||||
};
|
||||
|
||||
const getUnexpiredOpenIDSessionIdToken = (idToken) => {
|
||||
if (!idToken) {
|
||||
return;
|
||||
|
|
@ -133,6 +236,7 @@ const sendVerificationEmail = async (user) => {
|
|||
await createToken({
|
||||
userId: user._id,
|
||||
email: user.email,
|
||||
type: AuthTokenTypes.EMAIL_VERIFICATION,
|
||||
token: hash,
|
||||
createdAt: Date.now(),
|
||||
expiresIn: 900,
|
||||
|
|
@ -161,11 +265,11 @@ const verifyEmail = async (req) => {
|
|||
return { message: 'Email already verified', status: 'success' };
|
||||
}
|
||||
|
||||
let emailVerificationData = await findToken({ email: decodedEmail }, { sort: { createdAt: -1 } });
|
||||
const emailVerificationData = await findEmailVerificationToken(user);
|
||||
|
||||
if (!emailVerificationData) {
|
||||
logger.warn(`[verifyEmail] [No email verification data found] [Email: ${decodedEmail}]`);
|
||||
return new Error('Invalid or expired password reset token');
|
||||
return new Error('Invalid or expired email verification token');
|
||||
}
|
||||
|
||||
const isValid = bcrypt.compareSync(token, emailVerificationData.token);
|
||||
|
|
@ -184,7 +288,7 @@ const verifyEmail = async (req) => {
|
|||
return new Error('Failed to update user verification status');
|
||||
}
|
||||
|
||||
await deleteTokens({ token: emailVerificationData.token });
|
||||
await deleteTokens(getEmailVerificationTokenDeleteQuery(emailVerificationData));
|
||||
logger.info(`[verifyEmail] Email verification successful [Email: ${decodedEmail}]`);
|
||||
return { message: 'Email verification was successful', status: 'success' };
|
||||
};
|
||||
|
|
@ -337,12 +441,16 @@ const requestPasswordReset = async (req) => {
|
|||
};
|
||||
}
|
||||
|
||||
await deleteTokens({ userId: user._id });
|
||||
await Promise.all([
|
||||
deleteTokens({ userId: user._id, type: AuthTokenTypes.PASSWORD_RESET }),
|
||||
deleteTokens({ userId: user._id, email: null, identifier: null, type: null }),
|
||||
]);
|
||||
|
||||
const [resetToken, hash] = createTokenHash();
|
||||
|
||||
await createToken({
|
||||
userId: user._id,
|
||||
type: AuthTokenTypes.PASSWORD_RESET,
|
||||
token: hash,
|
||||
createdAt: Date.now(),
|
||||
expiresIn: 900,
|
||||
|
|
@ -386,12 +494,7 @@ const requestPasswordReset = async (req) => {
|
|||
* @returns
|
||||
*/
|
||||
const resetPassword = async (userId, token, password) => {
|
||||
let passwordResetToken = await findToken(
|
||||
{
|
||||
userId,
|
||||
},
|
||||
{ sort: { createdAt: -1 } },
|
||||
);
|
||||
const passwordResetToken = await findPasswordResetToken(userId);
|
||||
|
||||
if (!passwordResetToken) {
|
||||
return new Error('Invalid or expired password reset token');
|
||||
|
|
@ -419,7 +522,7 @@ const resetPassword = async (userId, token, password) => {
|
|||
});
|
||||
}
|
||||
|
||||
await deleteTokens({ token: passwordResetToken.token });
|
||||
await deleteTokens(getPasswordResetTokenDeleteQuery(passwordResetToken));
|
||||
logger.info(`[resetPassword] Password reset successful. [Email: ${user.email}]`);
|
||||
return { message: 'Password reset was successful' };
|
||||
};
|
||||
|
|
@ -724,7 +827,6 @@ const setOpenIDAuthTokens = (
|
|||
const resendVerificationEmail = async (req) => {
|
||||
try {
|
||||
const { email } = req.body;
|
||||
await deleteTokens({ email });
|
||||
const user = await findUser({ email }, 'email _id name');
|
||||
|
||||
if (!user) {
|
||||
|
|
@ -732,6 +834,8 @@ const resendVerificationEmail = async (req) => {
|
|||
return { status: 200, message: genericVerificationMessage };
|
||||
}
|
||||
|
||||
await deleteEmailVerificationTokens(user);
|
||||
|
||||
const [verifyToken, hash] = createTokenHash();
|
||||
|
||||
const verificationLink = `${
|
||||
|
|
@ -753,6 +857,7 @@ const resendVerificationEmail = async (req) => {
|
|||
await createToken({
|
||||
userId: user._id,
|
||||
email: user.email,
|
||||
type: AuthTokenTypes.EMAIL_VERIFICATION,
|
||||
token: hash,
|
||||
createdAt: Date.now(),
|
||||
expiresIn: 900,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue