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_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",
"meilisearch": "^0.33.0",
"mongoose": "^7.1.1",
"nodemailer": "^6.9.1",
"nodemailer": "^6.9.4",
"openai": "^3.2.1",
"openid-client": "^5.4.2",
"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';
@ -32,10 +32,10 @@ const getUserController = async (req, res) => {
const resetPasswordRequestController = async (req, res) => {
try {
const resetService = await requestPasswordReset(req.body.email);
if (resetService.link) {
return res.status(200).json(resetService);
} else {
if (resetService instanceof Error) {
return res.status(400).json(resetService);
} else {
return res.status(200).json(resetService);
}
} catch (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 { signedCookies = {} } = req;

View file

@ -18,6 +18,11 @@ router.get('/', async function (req, res) {
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
const registrationEnabled = process.env.ALLOW_REGISTRATION === '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({
appTitle,
@ -30,6 +35,7 @@ router.get('/', async function (req, res) {
serverDomain,
registrationEnabled,
socialLoginEnabled,
emailEnabled,
});
} catch (err) {
console.error(err);

View file

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

View file

@ -1,5 +1,3 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
const nodemailer = require('nodemailer');
const handlebars = require('handlebars');
const fs = require('fs');
@ -7,21 +5,19 @@ const path = require('path');
const sendEmail = async (email, subject, payload, template) => {
try {
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: 465,
service: process.env.EMAIL_SERVICE,
auth: {
user: process.env.EMAIL_USERNAME,
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 options = () => {
return {
from: process.env.FROM_EMAIL,
from: process.env.EMAIL_FROM,
to: email,
subject: subject,
html: compiledTemplate(payload),
@ -31,26 +27,17 @@ const sendEmail = async (email, subject, payload, template) => {
// Send email
transporter.sendMail(options(), (error, info) => {
if (error) {
console.log(error);
return error;
} else {
return res.status(200).json({
success: true,
});
console.log(info);
return info;
}
});
} catch (error) {
console.log(error);
return error;
}
};
/*
Example:
sendEmail(
"youremail@gmail.com,
"Email subject",
{ name: "Eze" },
"./templates/layouts/main.handlebars"
);
*/
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 { useRecoilValue } from 'recoil';
import store from '~/store';
import { localize } from '~/localization/Translation';
import {
useRequestPasswordResetMutation,
useGetStartupConfig,
TRequestPasswordReset,
TRequestPasswordResetResponse,
} from 'librechat-data-provider';
@ -17,15 +18,19 @@ function RequestPasswordReset() {
formState: { errors },
} = useForm<TRequestPasswordReset>();
const requestPasswordReset = useRequestPasswordResetMutation();
const [success, setSuccess] = useState<boolean>(false);
const config = useGetStartupConfig();
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) => {
requestPasswordReset.mutate(data, {
onSuccess: (data: TRequestPasswordResetResponse) => {
setSuccess(true);
console.log('emailEnabled: ', config.data?.emailEnabled);
if (!config.data?.emailEnabled) {
setResetLink(data.link);
}
},
onError: () => {
setRequestError(true);
@ -36,25 +41,33 @@ function RequestPasswordReset() {
});
};
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">
{localize(lang, 'com_auth_reset_password')}
</h1>
{success && (
<div
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700"
role="alert"
>
useEffect(() => {
if (requestPasswordReset.isSuccess) {
if (config.data?.emailEnabled) {
setHeaderText(localize(lang, 'com_auth_reset_password_link_sent'));
setBodyText(localize(lang, 'com_auth_reset_password_email_sent'));
} else {
setHeaderText(localize(lang, 'com_auth_reset_password'));
setBodyText(
<span>
{localize(lang, 'com_auth_click')}{' '}
<a className="text-green-600 hover:underline" href={resetLink}>
{localize(lang, 'com_auth_here')}
</a>{' '}
{localize(lang, 'com_auth_to_reset_your_password')}
{/* An email has been sent with instructions on how to reset your password. */}
</div>
)}
</span>,
);
}
} 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 && (
<div
className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
@ -63,6 +76,14 @@ function RequestPasswordReset() {
{localize(lang, 'com_auth_error_reset_password')}
</div>
)}
{bodyText ? (
<div
className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700"
role="alert"
>
{bodyText}
</div>
) : (
<form
className="mt-6"
aria-label="Password reset form"
@ -119,6 +140,7 @@ function RequestPasswordReset() {
</button>
</div>
</form>
)}
</div>
</div>
);

View file

@ -52,6 +52,9 @@ export default {
com_auth_password_forgot: 'Esqueceu a senha?',
com_auth_password_confirm: 'Confirmar senha',
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_create_account: 'Crie sua conta',
com_auth_error_create:

View file

@ -69,6 +69,9 @@ export default {
com_auth_click: 'Click',
com_auth_here: 'HERE',
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:
'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',

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_pattern: 'Debes ingresar una dirección de email válida',
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_required: 'Se requiere la contraseña',
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_confirm: 'Conferma password',
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_create_account: 'Crea il tuo account',
com_auth_error_create:

View file

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

118
package-lock.json generated
View file

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

View file

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