Compare commits

...

4 Commits

Author SHA1 Message Date
Danny Avila
302b28fc9b v0.7.4-rc1 (#3099)
* fix(openIdStrategy): return user object on new user creation

*  v0.7.4-rc1
2024-06-17 12:47:28 -04:00
Marco Beretta
dad25bd297 📝 docs: update README (#3093) 2024-06-17 07:51:13 -04:00
Marco Beretta
a338decf90 ✉️ fix: email address encoding in verification link (#3085)
Related to #3084

Implements URL encoding for email addresses in verification links and decodes them upon verification.

- **Encode email addresses** in `sendVerificationEmail` and `resendVerificationEmail` functions using `encodeURIComponent` to ensure special characters like `+` are correctly handled in the verification link.
- **Decode email addresses** in the `verifyEmail` function using `decodeURIComponent` to accurately retrieve and validate the email address from the verification link against the database.


---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/danny-avila/LibreChat/issues/3084?shareId=9c32df30-4156-4082-a3eb-fff54eaba5b3).
2024-06-16 16:05:53 -04:00
Danny Avila
2cf5228021 🕑 fix: Add Suspense to Connection Error Messages (#3074) 2024-06-15 16:16:06 -04:00
13 changed files with 86 additions and 19 deletions

View File

@@ -81,7 +81,7 @@ LibreChat brings together the future of assistant AIs with the revolutionary tec
With LibreChat, you no longer need to opt for ChatGPT Plus and can instead use free or pay-per-call APIs. We welcome contributions, cloning, and forking to enhance the capabilities of this advanced chatbot platform.
[![Watch the video](https://img.youtube.com/vi/YLVUW5UP9N0/maxresdefault.jpg)](https://www.youtube.com/watch?v=YLVUW5UP9N0)
[![Watch the video](https://img.youtube.com/vi/bSVHEbVPNl4/maxresdefault.jpg)](https://www.youtube.com/watch?v=bSVHEbVPNl4)
Click on the thumbnail to open the video☝
---

View File

@@ -56,10 +56,11 @@ const updateUser = async function (userId, updateData) {
* 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`.
* @param {boolean} [returnUser=false] - Whether to disable the TTL. Defaults to `true`.
* @returns {Promise<ObjectId>} 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 createUser = async (data, disableTTL = true, returnUser = false) => {
const userData = {
...data,
expiresAt: disableTTL ? null : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
@@ -71,6 +72,9 @@ const createUser = async (data, disableTTL = true) => {
try {
const user = await User.create(userData);
if (returnUser) {
return user.toObject();
}
return user._id;
} catch (error) {
if (error.code === 11000) {

View File

@@ -62,7 +62,7 @@ const sendVerificationEmail = async (user) => {
let verifyToken = crypto.randomBytes(32).toString('hex');
const hash = bcrypt.hashSync(verifyToken, 10);
const verificationLink = `${domains.client}/verify?token=${verifyToken}&email=${user.email}`;
const verificationLink = `${domains.client}/verify?token=${verifyToken}&email=${encodeURIComponent(user.email)}`;
await sendEmail({
email: user.email,
subject: 'Verify your email',
@@ -91,7 +91,7 @@ const sendVerificationEmail = async (user) => {
*/
const verifyEmail = async (req) => {
const { email, token } = req.body;
let emailVerificationData = await Token.findOne({ email });
let emailVerificationData = await Token.findOne({ email: decodeURIComponent(email) });
if (!emailVerificationData) {
logger.warn(`[verifyEmail] [No email verification data found] [Email: ${email}]`);
@@ -363,7 +363,7 @@ const resendVerificationEmail = async (req) => {
let verifyToken = crypto.randomBytes(32).toString('hex');
const hash = bcrypt.hashSync(verifyToken, 10);
const verificationLink = `${domains.client}/verify?token=${verifyToken}&email=${user.email}`;
const verificationLink = `${domains.client}/verify?token=${verifyToken}&email=${encodeURIComponent(user.email)}`;
await sendEmail({
email: user.email,

View File

@@ -164,8 +164,7 @@ async function setupOpenId() {
emailVerified: userinfo.email_verified || false,
name: fullName,
};
const userId = await createUser();
user._id = userId;
user = await createUser(user, true, true);
} else {
user.provider = 'openid';
user.openidId = userinfo.sub;

View File

@@ -5,6 +5,7 @@ import Plugin from '~/components/Messages/Content/Plugin';
import Error from '~/components/Messages/Content/Error';
import { DelayedRender } from '~/components/ui';
import EditMessage from './EditMessage';
import { useLocalize } from '~/hooks';
import Container from './Container';
import Markdown from './Markdown';
import { cn } from '~/utils';
@@ -14,6 +15,38 @@ export const ErrorMessage = ({
message,
className = '',
}: Pick<TDisplayProps, 'text' | 'className' | 'message'>) => {
const localize = useLocalize();
if (text === 'Error connecting to server, try refreshing the page.') {
console.log('error message', message);
return (
<Suspense
fallback={
<div className="text-message mb-[0.625rem] mt-1 flex min-h-[20px] flex-col items-start gap-3 overflow-x-auto">
<div className="markdown prose dark:prose-invert light w-full break-words dark:text-gray-100">
<div className="absolute">
<p className="relative">
<span className="result-thinking" />
</p>
</div>
</div>
</div>
}
>
<DelayedRender delay={5500}>
<Container message={message}>
<div
className={cn(
'rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200',
className,
)}
>
{localize('com_ui_error_connection')}
</div>
</Container>
</DelayedRender>
</Suspense>
);
}
return (
<Container message={message}>
<div

View File

@@ -58,7 +58,11 @@ export default function HoverButtons({
return null;
}
const { isCreatedByUser } = message;
const { isCreatedByUser, error } = message;
if (error) {
return null;
}
const onEdit = () => {
if (isEditing) {

View File

@@ -22,6 +22,7 @@ export default function Message(props: TMessageProps) {
edit,
index,
isLast,
assistant,
enterEdit,
handleScroll,
conversation,
@@ -44,6 +45,8 @@ export default function Message(props: TMessageProps) {
let messageLabel = '';
if (isCreatedByUser) {
messageLabel = UsernameDisplay ? user?.name || user?.username : localize('com_user_message');
} else if (assistant) {
messageLabel = assistant.name ?? 'Assistant';
} else {
messageLabel = message.sender;
}
@@ -61,7 +64,7 @@ export default function Message(props: TMessageProps) {
<div>
<div className="pt-0.5">
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
<Icon message={message} conversation={conversation} />
<Icon message={message} conversation={conversation} assistant={assistant} />
</div>
</div>
</div>

View File

@@ -1,14 +1,37 @@
import { useState, useEffect } from 'react';
import { useState, useRef, useEffect } from 'react';
import type { ReactNode } from 'react';
const useDelayedRender = (delay: number) => {
const [delayed, setDelayed] = useState(true);
const timerPromiseRef = useRef<Promise<void> | null>(null);
useEffect(() => {
const timeout = setTimeout(() => setDelayed(false), delay);
return () => clearTimeout(timeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (fn: () => ReactNode) => !delayed && fn();
if (delayed) {
const timerPromise = new Promise<void>((resolve) => {
const timeout = setTimeout(() => {
setDelayed(false);
resolve();
}, delay);
return () => {
clearTimeout(timeout);
};
});
timerPromiseRef.current = timerPromise;
}
return () => {
timerPromiseRef.current = null;
};
}, [delay, delayed]);
return (fn: () => ReactNode) => {
if (delayed && timerPromiseRef.current) {
throw timerPromiseRef.current;
}
return fn();
};
};
export default useDelayedRender;

View File

@@ -117,6 +117,7 @@ export default {
com_ui_instructions: 'Instructions',
com_ui_description: 'Description',
com_ui_error: 'Error',
com_ui_error_connection: 'Error connecting to server, try refreshing the page.',
com_ui_select: 'Select',
com_ui_input: 'Input',
com_ui_close: 'Close',

2
package-lock.json generated
View File

@@ -29325,7 +29325,7 @@
},
"packages/data-provider": {
"name": "librechat-data-provider",
"version": "0.6.7",
"version": "0.6.8",
"license": "ISC",
"dependencies": {
"@types/js-yaml": "^4.0.9",

View File

@@ -1,6 +1,6 @@
{
"name": "LibreChat",
"version": "0.7.3",
"version": "0.7.4-rc1",
"description": "",
"workspaces": [
"api",

View File

@@ -1,6 +1,6 @@
{
"name": "librechat-data-provider",
"version": "0.6.7",
"version": "0.6.8",
"description": "data services for librechat apps",
"main": "dist/index.js",
"module": "dist/index.es.js",

View File

@@ -786,7 +786,7 @@ export enum SettingsTabValues {
/** Enum for app-wide constants */
export enum Constants {
/** Key for the app's version. */
VERSION = 'v0.7.3',
VERSION = 'v0.7.4-rc1',
/** Key for the Custom Config's version (librechat.yaml). */
CONFIG_VERSION = '1.1.4',
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */