Feat email password reset (#730)

* change name of auth.service to AuthService

* Add emailEnabled to config api

* Setup email

* update nodemailer version

* add translations

* update .env.example

* clean up console.log's)

* refactor RequestPasswordReset component

* chore: rebuild package-lock.json

---------

Co-authored-by: Daniel Avila <messagedaniel@protonmail.com>
This commit is contained in:
Dan Orlando 2023-07-31 19:37:46 -07:00 committed by GitHub
parent 2faeebfae2
commit 30a49ae611
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 221 additions and 171 deletions

View file

@ -261,3 +261,13 @@ DISCORD_CALLBACK_URL=/oauth/discord/callback # this should be the same for every
DOMAIN_CLIENT=http://localhost:3080 DOMAIN_CLIENT=http://localhost:3080
DOMAIN_SERVER=http://localhost:3080 DOMAIN_SERVER=http://localhost:3080
###########################
# Email
###########################
# Email is used for password reset. Note that all 4 values must be set for email to work.
EMAIL_SERVICE= # eg. gmail
EMAIL_USERNAME= # eg. your email address if using gmail
EMAIL_PASSWORD= # eg. this is the "app password" if using gmail
EMAIL_FROM= # eg. email address for from field like noreply@librechat.ai

View file

@ -47,7 +47,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"meilisearch": "^0.33.0", "meilisearch": "^0.33.0",
"mongoose": "^7.1.1", "mongoose": "^7.1.1",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.4",
"openai": "^3.2.1", "openai": "^3.2.1",
"openid-client": "^5.4.2", "openid-client": "^5.4.2",
"passport": "^0.6.0", "passport": "^0.6.0",

View file

@ -1,4 +1,4 @@
const { registerUser, requestPasswordReset, resetPassword } = require('../services/auth.service'); const { registerUser, requestPasswordReset, resetPassword } = require('../services/AuthService');
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
@ -32,10 +32,10 @@ const getUserController = async (req, res) => {
const resetPasswordRequestController = async (req, res) => { const resetPasswordRequestController = async (req, res) => {
try { try {
const resetService = await requestPasswordReset(req.body.email); const resetService = await requestPasswordReset(req.body.email);
if (resetService.link) { if (resetService instanceof Error) {
return res.status(200).json(resetService);
} else {
return res.status(400).json(resetService); return res.status(400).json(resetService);
} else {
return res.status(200).json(resetService);
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);

View file

@ -1,4 +1,4 @@
const { logoutUser } = require('../../services/auth.service'); const { logoutUser } = require('../../services/AuthService');
const logoutController = async (req, res) => { const logoutController = async (req, res) => {
const { signedCookies = {} } = req; const { signedCookies = {} } = req;

View file

@ -18,6 +18,11 @@ router.get('/', async function (req, res) {
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080'; const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
const registrationEnabled = process.env.ALLOW_REGISTRATION === 'true'; const registrationEnabled = process.env.ALLOW_REGISTRATION === 'true';
const socialLoginEnabled = process.env.ALLOW_SOCIAL_LOGIN === 'true'; const socialLoginEnabled = process.env.ALLOW_SOCIAL_LOGIN === 'true';
const emailEnabled =
!!process.env.EMAIL_SERVICE &&
!!process.env.EMAIL_USERNAME &&
!!process.env.EMAIL_PASSWORD &&
!!process.env.EMAIL_FROM;
return res.status(200).send({ return res.status(200).send({
appTitle, appTitle,
@ -30,6 +35,7 @@ router.get('/', async function (req, res) {
serverDomain, serverDomain,
registrationEnabled, registrationEnabled,
socialLoginEnabled, socialLoginEnabled,
emailEnabled,
}); });
} catch (err) { } catch (err) {
console.error(err); console.error(err);

View file

@ -125,16 +125,26 @@ const requestPasswordReset = async (email) => {
const link = `${domains.client}/reset-password?token=${resetToken}&userId=${user._id}`; const link = `${domains.client}/reset-password?token=${resetToken}&userId=${user._id}`;
sendEmail( const emailEnabled =
user.email, !!process.env.EMAIL_SERVICE &&
'Password Reset Request', !!process.env.EMAIL_USERNAME &&
{ !!process.env.EMAIL_PASSWORD &&
name: user.name, !!process.env.EMAIL_FROM;
link: link,
}, if (emailEnabled) {
'./template/requestResetPassword.handlebars', sendEmail(
); user.email,
return { link }; 'Password Reset Request',
{
name: user.name,
link: link,
},
'requestPasswordReset.handlebars',
);
return { link: '' };
} else {
return { link };
}
}; };
/** /**
@ -170,7 +180,7 @@ const resetPassword = async (userId, token, password) => {
{ {
name: user.name, name: user.name,
}, },
'./template/resetPassword.handlebars', 'resetPassword.handlebars',
); );
await passwordResetToken.deleteOne(); await passwordResetToken.deleteOne();

View file

@ -1,5 +1,3 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer');
const handlebars = require('handlebars'); const handlebars = require('handlebars');
const fs = require('fs'); const fs = require('fs');
@ -7,21 +5,19 @@ const path = require('path');
const sendEmail = async (email, subject, payload, template) => { const sendEmail = async (email, subject, payload, template) => {
try { try {
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST, service: process.env.EMAIL_SERVICE,
port: 465,
auth: { auth: {
user: process.env.EMAIL_USERNAME, user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD, pass: process.env.EMAIL_PASSWORD,
}, },
}); });
const source = fs.readFileSync(path.join(__dirname, template), 'utf8'); const source = fs.readFileSync(path.join(__dirname, 'emails', template), 'utf8');
const compiledTemplate = handlebars.compile(source); const compiledTemplate = handlebars.compile(source);
const options = () => { const options = () => {
return { return {
from: process.env.FROM_EMAIL, from: process.env.EMAIL_FROM,
to: email, to: email,
subject: subject, subject: subject,
html: compiledTemplate(payload), html: compiledTemplate(payload),
@ -31,26 +27,17 @@ const sendEmail = async (email, subject, payload, template) => {
// Send email // Send email
transporter.sendMail(options(), (error, info) => { transporter.sendMail(options(), (error, info) => {
if (error) { if (error) {
console.log(error);
return error; return error;
} else { } else {
return res.status(200).json({ console.log(info);
success: true, return info;
});
} }
}); });
} catch (error) { } catch (error) {
console.log(error);
return error; return error;
} }
}; };
/*
Example:
sendEmail(
"youremail@gmail.com,
"Email subject",
{ name: "Eze" },
"./templates/layouts/main.handlebars"
);
*/
module.exports = sendEmail; module.exports = sendEmail;

View file

@ -1,10 +1,11 @@
import { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import store from '~/store'; import store from '~/store';
import { localize } from '~/localization/Translation'; import { localize } from '~/localization/Translation';
import { import {
useRequestPasswordResetMutation, useRequestPasswordResetMutation,
useGetStartupConfig,
TRequestPasswordReset, TRequestPasswordReset,
TRequestPasswordResetResponse, TRequestPasswordResetResponse,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
@ -17,15 +18,19 @@ function RequestPasswordReset() {
formState: { errors }, formState: { errors },
} = useForm<TRequestPasswordReset>(); } = useForm<TRequestPasswordReset>();
const requestPasswordReset = useRequestPasswordResetMutation(); const requestPasswordReset = useRequestPasswordResetMutation();
const [success, setSuccess] = useState<boolean>(false); const config = useGetStartupConfig();
const [requestError, setRequestError] = useState<boolean>(false); const [requestError, setRequestError] = useState<boolean>(false);
const [resetLink, setResetLink] = useState<string>(''); const [resetLink, setResetLink] = useState<string | undefined>(undefined);
const [headerText, setHeaderText] = useState<string>('');
const [bodyText, setBodyText] = useState<React.ReactNode | undefined>(undefined);
const onSubmit = (data: TRequestPasswordReset) => { const onSubmit = (data: TRequestPasswordReset) => {
requestPasswordReset.mutate(data, { requestPasswordReset.mutate(data, {
onSuccess: (data: TRequestPasswordResetResponse) => { onSuccess: (data: TRequestPasswordResetResponse) => {
setSuccess(true); console.log('emailEnabled: ', config.data?.emailEnabled);
setResetLink(data.link); if (!config.data?.emailEnabled) {
setResetLink(data.link);
}
}, },
onError: () => { onError: () => {
setRequestError(true); setRequestError(true);
@ -36,25 +41,33 @@ function RequestPasswordReset() {
}); });
}; };
return ( useEffect(() => {
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0"> if (requestPasswordReset.isSuccess) {
<div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg"> if (config.data?.emailEnabled) {
<h1 className="mb-4 text-center text-3xl font-semibold"> setHeaderText(localize(lang, 'com_auth_reset_password_link_sent'));
{localize(lang, 'com_auth_reset_password')} setBodyText(localize(lang, 'com_auth_reset_password_email_sent'));
</h1> } else {
{success && ( setHeaderText(localize(lang, 'com_auth_reset_password'));
<div setBodyText(
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700" <span>
role="alert"
>
{localize(lang, 'com_auth_click')}{' '} {localize(lang, 'com_auth_click')}{' '}
<a className="text-green-600 hover:underline" href={resetLink}> <a className="text-green-600 hover:underline" href={resetLink}>
{localize(lang, 'com_auth_here')} {localize(lang, 'com_auth_here')}
</a>{' '} </a>{' '}
{localize(lang, 'com_auth_to_reset_your_password')} {localize(lang, 'com_auth_to_reset_your_password')}
{/* An email has been sent with instructions on how to reset your password. */} </span>,
</div> );
)} }
} else {
setHeaderText(localize(lang, 'com_auth_reset_password'));
setBodyText(undefined);
}
}, [requestPasswordReset.isSuccess, config.data?.emailEnabled, resetLink, lang]);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="mb-4 text-center text-3xl font-semibold">{headerText}</h1>
{requestError && ( {requestError && (
<div <div
className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700" className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
@ -63,62 +76,71 @@ function RequestPasswordReset() {
{localize(lang, 'com_auth_error_reset_password')} {localize(lang, 'com_auth_error_reset_password')}
</div> </div>
)} )}
<form {bodyText ? (
className="mt-6" <div
aria-label="Password reset form" className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700"
method="POST" role="alert"
onSubmit={handleSubmit(onSubmit)} >
> {bodyText}
<div className="mb-2"> </div>
<div className="relative"> ) : (
<input <form
type="email" className="mt-6"
id="email" aria-label="Password reset form"
autoComplete="off" method="POST"
aria-label={localize(lang, 'com_auth_email')} onSubmit={handleSubmit(onSubmit)}
{...register('email', { >
required: localize(lang, 'com_auth_email_required'), <div className="mb-2">
minLength: { <div className="relative">
value: 3, <input
message: localize(lang, 'com_auth_email_min_length'), type="email"
}, id="email"
maxLength: { autoComplete="off"
value: 120, aria-label={localize(lang, 'com_auth_email')}
message: localize(lang, 'com_auth_email_max_length'), {...register('email', {
}, required: localize(lang, 'com_auth_email_required'),
pattern: { minLength: {
value: /\S+@\S+\.\S+/, value: 3,
message: localize(lang, 'com_auth_email_pattern'), message: localize(lang, 'com_auth_email_min_length'),
}, },
})} maxLength: {
aria-invalid={!!errors.email} value: 120,
className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0" message: localize(lang, 'com_auth_email_max_length'),
placeholder=" " },
></input> pattern: {
<label value: /\S+@\S+\.\S+/,
htmlFor="email" message: localize(lang, 'com_auth_email_pattern'),
className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500" },
> })}
{localize(lang, 'com_auth_email_address')} aria-invalid={!!errors.email}
</label> className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" "
></input>
<label
htmlFor="email"
className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
>
{localize(lang, 'com_auth_email_address')}
</label>
</div>
{errors.email && (
<span role="alert" className="mt-1 text-sm text-red-600">
{/* @ts-ignore not sure why */}
{errors.email.message}
</span>
)}
</div> </div>
{errors.email && ( <div className="mt-6">
<span role="alert" className="mt-1 text-sm text-red-600"> <button
{/* @ts-ignore not sure why */} type="submit"
{errors.email.message} disabled={!!errors.email}
</span> className="w-full rounded-sm border border-transparent bg-green-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-600 focus:outline-none active:bg-green-500"
)} >
</div> {localize(lang, 'com_auth_continue')}
<div className="mt-6"> </button>
<button </div>
type="submit" </form>
disabled={!!errors.email} )}
className="w-full rounded-sm border border-transparent bg-green-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-600 focus:outline-none active:bg-green-500"
>
{localize(lang, 'com_auth_continue')}
</button>
</div>
</form>
</div> </div>
</div> </div>
); );

View file

@ -52,6 +52,9 @@ export default {
com_auth_password_forgot: 'Esqueceu a senha?', com_auth_password_forgot: 'Esqueceu a senha?',
com_auth_password_confirm: 'Confirmar senha', com_auth_password_confirm: 'Confirmar senha',
com_auth_password_not_match: 'As senhas não correspondem', com_auth_password_not_match: 'As senhas não correspondem',
com_auth_reset_password_link_sent: 'Link para redefinir a senha enviado',
com_auth_reset_password_email_sent:
'Um email foi enviado para com instruções para redefinir sua senha.',
com_auth_continue: 'Continuar', com_auth_continue: 'Continuar',
com_auth_create_account: 'Crie sua conta', com_auth_create_account: 'Crie sua conta',
com_auth_error_create: com_auth_error_create:

View file

@ -69,6 +69,9 @@ export default {
com_auth_click: 'Click', com_auth_click: 'Click',
com_auth_here: 'HERE', com_auth_here: 'HERE',
com_auth_to_reset_your_password: 'to reset your password.', com_auth_to_reset_your_password: 'to reset your password.',
com_auth_reset_password_link_sent: 'Email Sent',
com_auth_reset_password_email_sent:
'An email has been sent to you with further instructions to reset your password.',
com_auth_error_reset_password: com_auth_error_reset_password:
'There was a problem resetting your password. There was no user found with the email address provided. Please try again.', 'There was a problem resetting your password. There was no user found with the email address provided. Please try again.',
com_auth_reset_password_success: 'Password Reset Success', com_auth_reset_password_success: 'Password Reset Success',

View file

@ -46,6 +46,8 @@ export default {
com_auth_email_max_length: 'El email no debe tener más de 120 caracteres', com_auth_email_max_length: 'El email no debe tener más de 120 caracteres',
com_auth_email_pattern: 'Debes ingresar una dirección de email válida', com_auth_email_pattern: 'Debes ingresar una dirección de email válida',
com_auth_email_address: 'Dirección de email', com_auth_email_address: 'Dirección de email',
com_auth_reset_password_link_sent: 'Enlace para restablecer la contraseña enviado',
com_auth_reset_password_email_sent: 'Se ha enviado un correo electrónico con instrucciones.',
com_auth_password: 'Contraseña', com_auth_password: 'Contraseña',
com_auth_password_required: 'Se requiere la contraseña', com_auth_password_required: 'Se requiere la contraseña',
com_auth_password_min_length: 'La contraseña debe tener al menos 8 caracteres', com_auth_password_min_length: 'La contraseña debe tener al menos 8 caracteres',

View file

@ -52,6 +52,9 @@ export default {
com_auth_password_forgot: 'Password dimenticata?', com_auth_password_forgot: 'Password dimenticata?',
com_auth_password_confirm: 'Conferma password', com_auth_password_confirm: 'Conferma password',
com_auth_password_not_match: 'Le password non corrispondono', com_auth_password_not_match: 'Le password non corrispondono',
com_auth_reset_password_link_sent: 'Link per reimpostare la password inviato',
com_auth_reset_password_email_sent:
'Ti abbiamo inviato un\'email con un link per reimpostare la password.',
com_auth_continue: 'Continua', com_auth_continue: 'Continua',
com_auth_create_account: 'Crea il tuo account', com_auth_create_account: 'Crea il tuo account',
com_auth_error_create: com_auth_error_create:

View file

@ -52,6 +52,8 @@ export default {
com_auth_continue: '继续', com_auth_continue: '继续',
com_auth_create_account: '创建账号', com_auth_create_account: '创建账号',
com_auth_error_create: '注册账户过程中出现错误,请重试。', com_auth_error_create: '注册账户过程中出现错误,请重试。',
com_auth_reset_password_link_sent: '重置密码链接已发送至邮箱',
com_auth_reset_password_email_sent: '重置密码邮件已发送至邮箱',
com_auth_full_name: '姓名', com_auth_full_name: '姓名',
com_auth_name_required: '姓名为必填项', com_auth_name_required: '姓名为必填项',
com_auth_name_min_length: '姓名至少3个字符', com_auth_name_min_length: '姓名至少3个字符',

118
package-lock.json generated
View file

@ -72,7 +72,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"meilisearch": "^0.33.0", "meilisearch": "^0.33.0",
"mongoose": "^7.1.1", "mongoose": "^7.1.1",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.4",
"openai": "^3.2.1", "openai": "^3.2.1",
"openid-client": "^5.4.2", "openid-client": "^5.4.2",
"passport": "^0.6.0", "passport": "^0.6.0",
@ -243,9 +243,9 @@
} }
}, },
"node_modules/@anthropic-ai/sdk": { "node_modules/@anthropic-ai/sdk": {
"version": "0.5.8", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.5.8.tgz", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.5.9.tgz",
"integrity": "sha512-iHenjcE2Q/az6VZiP1DueOSvKNRmxsly6Rx2yjJBoy7OBYVFGVjEdgs2mPQHtTX0ibKAR7tPq6F6MQbKDPWcKg==", "integrity": "sha512-9/TYca4qSe0xG40LLNf5vemybw5JAKF5OE6Eiyc+O+h3+VGGPeOKo+1SHaWBP5zS7bGX2o3Ne6EonPWyh9oNqA==",
"dependencies": { "dependencies": {
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4", "@types/node-fetch": "^2.6.4",
@ -7472,9 +7472,9 @@
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.2.17", "version": "18.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.17.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz",
"integrity": "sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==", "integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@ -7761,12 +7761,12 @@
} }
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz",
"integrity": "sha512-pwXDog5nwwvSIzwrvYYmA2Ljcd/ZNlcsSG2Q9CNDBwnsd55UGAyr2doXtB5j+2uymRCnCfExlznzzSFbBRcoCg==", "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.9",
"@babel/plugin-transform-react-jsx-self": "^7.22.5", "@babel/plugin-transform-react-jsx-self": "^7.22.5",
"@babel/plugin-transform-react-jsx-source": "^7.22.5", "@babel/plugin-transform-react-jsx-source": "^7.22.5",
"react-refresh": "^0.14.0" "react-refresh": "^0.14.0"
@ -9485,9 +9485,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.21.9", "version": "4.21.10",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -9504,9 +9504,9 @@
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001503", "caniuse-lite": "^1.0.30001517",
"electron-to-chromium": "^1.4.431", "electron-to-chromium": "^1.4.477",
"node-releases": "^2.0.12", "node-releases": "^2.0.13",
"update-browserslist-db": "^1.0.11" "update-browserslist-db": "^1.0.11"
}, },
"bin": { "bin": {
@ -9704,9 +9704,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001517", "version": "1.0.30001518",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz",
"integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "integrity": "sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -10776,9 +10776,9 @@
} }
}, },
"node_modules/dedent": { "node_modules/dedent": {
"version": "1.3.0", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.3.0.tgz", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
"integrity": "sha512-7glNLfvdsMzZm3FpRY1CHuI2lbYDR+71YmrhmTZjYFD5pfT0ACgnGRdrrC9Mk2uICnzkcdelCx5at787UDGOvg==", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
"dev": true, "dev": true,
"peerDependencies": { "peerDependencies": {
"babel-plugin-macros": "^3.1.0" "babel-plugin-macros": "^3.1.0"
@ -11177,9 +11177,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.477", "version": "1.4.478",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.477.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.478.tgz",
"integrity": "sha512-shUVy6Eawp33dFBFIoYbIwLHrX0IZ857AlH9ug2o4rvbWmpaCUdBpQ5Zw39HRrfzAFm4APJE9V+E2A/WB0YqJw==", "integrity": "sha512-qjTA8djMXd+ruoODDFGnRCRBpID+AAfYWCyGtYTNhsuwxI19s8q19gbjKTwRS5z/LyVf5wICaIiPQGLekmbJbA==",
"dev": true "dev": true
}, },
"node_modules/elliptic": { "node_modules/elliptic": {
@ -17274,9 +17274,9 @@
} }
}, },
"node_modules/langsmith": { "node_modules/langsmith": {
"version": "0.0.15", "version": "0.0.16",
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.0.15.tgz", "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.0.16.tgz",
"integrity": "sha512-SVLJEm94aK9mmqE3z2wlpzLjPNodnPJHp1MZdmXG2ysUWSFsRVuGkd6A/GTzlEZ6OofyJSOg6tcNFJSFOi13QQ==", "integrity": "sha512-HD97KJaSpCcuixbjfRhpSFdo5rWz28OJiUVs5uBRZDKUN2Amg4PWd0NFzGO3xC8osnjPPRvgH9by6Ige79hjxQ==",
"dependencies": { "dependencies": {
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"commander": "^10.0.1", "commander": "^10.0.1",
@ -26550,7 +26550,7 @@
}, },
"packages/data-provider": { "packages/data-provider": {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.1.1", "version": "0.1.2",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@tanstack/react-query": "^4.28.0", "@tanstack/react-query": "^4.28.0",
@ -26666,9 +26666,9 @@
} }
}, },
"@anthropic-ai/sdk": { "@anthropic-ai/sdk": {
"version": "0.5.8", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.5.8.tgz", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.5.9.tgz",
"integrity": "sha512-iHenjcE2Q/az6VZiP1DueOSvKNRmxsly6Rx2yjJBoy7OBYVFGVjEdgs2mPQHtTX0ibKAR7tPq6F6MQbKDPWcKg==", "integrity": "sha512-9/TYca4qSe0xG40LLNf5vemybw5JAKF5OE6Eiyc+O+h3+VGGPeOKo+1SHaWBP5zS7bGX2o3Ne6EonPWyh9oNqA==",
"requires": { "requires": {
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4", "@types/node-fetch": "^2.6.4",
@ -30080,7 +30080,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"meilisearch": "^0.33.0", "meilisearch": "^0.33.0",
"mongoose": "^7.1.1", "mongoose": "^7.1.1",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.4",
"nodemon": "^2.0.20", "nodemon": "^2.0.20",
"openai": "^3.2.1", "openai": "^3.2.1",
"openid-client": "^5.4.2", "openid-client": "^5.4.2",
@ -31735,9 +31735,9 @@
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
}, },
"@types/react": { "@types/react": {
"version": "18.2.17", "version": "18.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.17.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz",
"integrity": "sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==", "integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@ -31935,12 +31935,12 @@
} }
}, },
"@vitejs/plugin-react": { "@vitejs/plugin-react": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.3.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz",
"integrity": "sha512-pwXDog5nwwvSIzwrvYYmA2Ljcd/ZNlcsSG2Q9CNDBwnsd55UGAyr2doXtB5j+2uymRCnCfExlznzzSFbBRcoCg==", "integrity": "sha512-7wU921ABnNYkETiMaZy7XqpueMnpu5VxvVps13MjmCo+utBdD79sZzrApHawHtVX66cCJQQTXFcjH0y9dSUK8g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.9",
"@babel/plugin-transform-react-jsx-self": "^7.22.5", "@babel/plugin-transform-react-jsx-self": "^7.22.5",
"@babel/plugin-transform-react-jsx-source": "^7.22.5", "@babel/plugin-transform-react-jsx-source": "^7.22.5",
"react-refresh": "^0.14.0" "react-refresh": "^0.14.0"
@ -33302,14 +33302,14 @@
} }
}, },
"browserslist": { "browserslist": {
"version": "4.21.9", "version": "4.21.10",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-lite": "^1.0.30001503", "caniuse-lite": "^1.0.30001517",
"electron-to-chromium": "^1.4.431", "electron-to-chromium": "^1.4.477",
"node-releases": "^2.0.12", "node-releases": "^2.0.13",
"update-browserslist-db": "^1.0.11" "update-browserslist-db": "^1.0.11"
} }
}, },
@ -33441,9 +33441,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001517", "version": "1.0.30001518",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz",
"integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", "integrity": "sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==",
"dev": true "dev": true
}, },
"ccount": { "ccount": {
@ -34230,9 +34230,9 @@
} }
}, },
"dedent": { "dedent": {
"version": "1.3.0", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.3.0.tgz", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
"integrity": "sha512-7glNLfvdsMzZm3FpRY1CHuI2lbYDR+71YmrhmTZjYFD5pfT0ACgnGRdrrC9Mk2uICnzkcdelCx5at787UDGOvg==", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
@ -34538,9 +34538,9 @@
} }
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.4.477", "version": "1.4.478",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.477.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.478.tgz",
"integrity": "sha512-shUVy6Eawp33dFBFIoYbIwLHrX0IZ857AlH9ug2o4rvbWmpaCUdBpQ5Zw39HRrfzAFm4APJE9V+E2A/WB0YqJw==", "integrity": "sha512-qjTA8djMXd+ruoODDFGnRCRBpID+AAfYWCyGtYTNhsuwxI19s8q19gbjKTwRS5z/LyVf5wICaIiPQGLekmbJbA==",
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
@ -38876,9 +38876,9 @@
} }
}, },
"langsmith": { "langsmith": {
"version": "0.0.15", "version": "0.0.16",
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.0.15.tgz", "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.0.16.tgz",
"integrity": "sha512-SVLJEm94aK9mmqE3z2wlpzLjPNodnPJHp1MZdmXG2ysUWSFsRVuGkd6A/GTzlEZ6OofyJSOg6tcNFJSFOi13QQ==", "integrity": "sha512-HD97KJaSpCcuixbjfRhpSFdo5rWz28OJiUVs5uBRZDKUN2Amg4PWd0NFzGO3xC8osnjPPRvgH9by6Ige79hjxQ==",
"requires": { "requires": {
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"commander": "^10.0.1", "commander": "^10.0.1",

View file

@ -257,6 +257,7 @@ export type TStartupConfig = {
serverDomain: string; serverDomain: string;
registrationEnabled: boolean; registrationEnabled: boolean;
socialLoginEnabled: boolean; socialLoginEnabled: boolean;
emailEnabled: boolean;
}; };
export type TRefreshTokenResponse = { export type TRefreshTokenResponse = {
@ -265,7 +266,8 @@ export type TRefreshTokenResponse = {
}; };
export type TRequestPasswordResetResponse = { export type TRequestPasswordResetResponse = {
link: string; link?: string;
message?: string;
}; };
export type File = { export type File = {