diff --git a/api/models/Message.js b/api/models/Message.js index e651b20ad0..bdeef05718 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -2,6 +2,7 @@ const { z } = require('zod'); const Message = require('./schema/messageSchema'); const { logger } = require('~/config'); +// Validate conversation ID as a UUID (if your conversation IDs follow UUID format) const idSchema = z.string().uuid(); /** @@ -28,8 +29,11 @@ const idSchema = z.string().uuid(); * @param {string} [params.plugin] - Plugin associated with the message. * @param {string[]} [params.plugins] - An array of plugins associated with the message. * @param {string} [params.model] - The model used to generate the message. - * @param {Object} [metadata] - Additional metadata for this operation - * @param {string} [metadata.context] - The context of the operation + * @param {string} [params.iv] - (Optional) Base64-encoded initialization vector for encryption. + * @param {string} [params.authTag] - (Optional) Base64-encoded authentication tag from AES-GCM. + * @param {string} [params.encryptedKey] - (Optional) Base64-encoded AES key encrypted with RSA. + * @param {Object} [metadata] - Additional metadata for this operation. + * @param {string} [metadata.context] - The context of the operation. * @returns {Promise} The updated or newly inserted message document. * @throws {Error} If there is an error in saving the message. */ @@ -51,6 +55,9 @@ async function saveMessage(req, params, metadata) { ...params, user: req.user.id, messageId: params.newMessageId || params.messageId, + iv: params.iv ?? null, + authTag: params.authTag ?? null, + encryptedKey: params.encryptedKey ?? null, }; if (req?.body?.isTemporary) { @@ -90,7 +97,12 @@ async function bulkSaveMessages(messages, overrideTimestamp = false) { const bulkOps = messages.map((message) => ({ updateOne: { filter: { messageId: message.messageId }, - update: message, + update: { + ...message, + iv: message.iv ?? null, + authTag: message.authTag ?? null, + encryptedKey: message.encryptedKey ?? null, + }, timestamps: !overrideTimestamp, upsert: true, }, @@ -119,14 +131,7 @@ async function bulkSaveMessages(messages, overrideTimestamp = false) { * @returns {Promise} The updated or newly inserted message document. * @throws {Error} If there is an error in saving the message. */ -async function recordMessage({ - user, - endpoint, - messageId, - conversationId, - parentMessageId, - ...rest -}) { +async function recordMessage({ user, endpoint, messageId, conversationId, parentMessageId, ...rest }) { try { // No parsing of convoId as may use threadId const message = { @@ -136,6 +141,9 @@ async function recordMessage({ conversationId, parentMessageId, ...rest, + iv: rest.iv ?? null, + authTag: rest.authTag ?? null, + encryptedKey: rest.encryptedKey ?? null, }; return await Message.findOneAndUpdate({ user, messageId }, message, { @@ -190,12 +198,15 @@ async function updateMessageText(req, { messageId, text }) { async function updateMessage(req, message, metadata) { try { const { messageId, ...update } = message; + // Ensure encryption fields are explicitly updated (if provided) + update.iv = update.iv ?? null; + update.authTag = update.authTag ?? null; + update.encryptedKey = update.encryptedKey ?? null; + const updatedMessage = await Message.findOneAndUpdate( { messageId, user: req.user.id }, update, - { - new: true, - }, + { new: true }, ); if (!updatedMessage) { @@ -225,11 +236,11 @@ async function updateMessage(req, message, metadata) { * * @async * @function deleteMessagesSince - * @param {Object} params - The parameters object. * @param {Object} req - The request object. + * @param {Object} params - The parameters object. * @param {string} params.messageId - The unique identifier for the message. * @param {string} params.conversationId - The identifier of the conversation. - * @returns {Promise} The number of deleted messages. + * @returns {Promise} The number of deleted messages. * @throws {Error} If there is an error in deleting messages. */ async function deleteMessagesSince(req, { messageId, conversationId }) { @@ -263,7 +274,6 @@ async function getMessages(filter, select) { if (select) { return await Message.find(filter).select(select).sort({ createdAt: 1 }).lean(); } - return await Message.find(filter).sort({ createdAt: 1 }).lean(); } catch (err) { logger.error('Error getting messages:', err); @@ -281,10 +291,7 @@ async function getMessages(filter, select) { */ async function getMessage({ user, messageId }) { try { - return await Message.findOne({ - user, - messageId, - }).lean(); + return await Message.findOne({ user, messageId }).lean(); } catch (err) { logger.error('Error getting message:', err); throw err; diff --git a/api/models/schema/messageSchema.js b/api/models/schema/messageSchema.js index 21346a2129..a8683b8a30 100644 --- a/api/models/schema/messageSchema.js +++ b/api/models/schema/messageSchema.js @@ -54,11 +54,6 @@ const messageSchema = mongoose.Schema( type: String, meiliIndex: true, }, - // If the message is encrypted (and stored in 'text'), - // then this field should hold the IV used during encryption. - messageEncryptionIV: { - type: String, - }, summary: { type: String, }, @@ -142,6 +137,18 @@ const messageSchema = mongoose.Schema( expiredAt: { type: Date, }, + iv: { + type: String, + default: null, + }, + authTag: { + type: String, + default: null, + }, + encryptedKey: { + type: String, + default: null, + }, }, { timestamps: true }, ); diff --git a/api/models/schema/shareSchema.js b/api/models/schema/shareSchema.js index 4faf114033..12699a39ec 100644 --- a/api/models/schema/shareSchema.js +++ b/api/models/schema/shareSchema.js @@ -23,13 +23,6 @@ const shareSchema = mongoose.Schema( type: Boolean, default: true, }, - // --- Field for re-encrypting the conversation key for the forked user --- - encryptionKeys: [ - { - user: { type: String, index: true }, - encryptedKey: { type: String }, - }, - ], }, { timestamps: true }, ); diff --git a/api/models/schema/userSchema.js b/api/models/schema/userSchema.js index db671bb927..297fb78e7d 100644 --- a/api/models/schema/userSchema.js +++ b/api/models/schema/userSchema.js @@ -27,10 +27,10 @@ const { SystemRoles } = require('librechat-data-provider'); * @property {Array} [plugins=[]] - List of plugins used by the user * @property {Array.} [refreshToken] - List of sessions with refresh tokens * @property {Date} [expiresAt] - Optional expiration date of the file - * @property {string} [encryptionPublicKey] - The user's public key for E2EE (client-generated) - * @property {string} [encryptedPrivateKey] - The user's private key encrypted with a user-defined passphrase - * @property {string} [encryptionSalt] - The salt used for PBKDF2 during encryption - * @property {string} [encryptionIV] - The initialization vector used for encryption (AES-GCM) + * @property {string} [encryptionPublicKey] - The user's encryption public key + * @property {string} [encryptedPrivateKey] - The user's encrypted private key + * @property {string} [encryptionSalt] - The salt used for key derivation (e.g., PBKDF2) + * @property {string} [encryptionIV] - The IV used for encrypting the private key * @property {Date} [createdAt] - Date when the user was created (added by timestamps) * @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps) */ @@ -136,26 +136,21 @@ const userSchema = mongoose.Schema( type: Boolean, default: false, }, - // --- New Fields for E2EE --- encryptionPublicKey: { type: String, - required: false, - // Provided by the client after key generation. + default: null, }, encryptedPrivateKey: { type: String, - required: false, - // The private key encrypted on the client with the user’s encryption passphrase. + default: null, }, encryptionSalt: { type: String, - required: false, - // Salt used for PBKDF2 when encrypting the private key. + default: null, }, encryptionIV: { type: String, - required: false, - // IV used for AES-GCM encryption of the private key. + default: null, }, }, diff --git a/api/server/controllers/AskController.js b/api/server/controllers/AskController.js index 2df6f34ede..04939d3fcc 100644 --- a/api/server/controllers/AskController.js +++ b/api/server/controllers/AskController.js @@ -4,6 +4,13 @@ const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); +let crypto; +try { + crypto = require('crypto'); +} catch (err) { + logger.error('[openidStrategy] crypto support is disabled!', err); +} + const AskController = async (req, res, next, initializeClient, addTitle) => { let { text, @@ -74,14 +81,8 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { res.on('close', () => { logger.debug('[AskController] Request closed'); - if (!abortController) { - return; - } else if (abortController.signal.aborted) { - return; - } else if (abortController.requestCompleted) { - return; - } - + if (!abortController) {return;} + if (abortController.signal.aborted || abortController.requestCompleted) {return;} abortController.abort(); logger.debug('[AskController] Request aborted on close'); }); @@ -95,10 +96,7 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { onStart, abortController, progressCallback, - progressOptions: { - res, - // parentMessageId: overrideParentMessageId || userMessageId, - }, + progressOptions: { res }, }; /** @type {TMessage} */ @@ -115,6 +113,58 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { delete userMessage.image_urls; } + // --- Encryption Branch --- + // Only encrypt if the user has set up encryption (i.e. non-empty encryptionPublicKey) + if ( + req.user.encryptionPublicKey && + req.user.encryptionPublicKey.trim() !== '' && + response.text && + crypto + ) { + try { + // Reconstruct the user's RSA public key in PEM format. + const pubKeyBase64 = req.user.encryptionPublicKey; + const pemPublicKey = `-----BEGIN PUBLIC KEY-----\n${pubKeyBase64.match(/.{1,64}/g).join('\n')}\n-----END PUBLIC KEY-----`; + + // Generate a random 256-bit AES key and a 12-byte IV. + const aesKey = crypto.randomBytes(32); + const iv = crypto.randomBytes(12); + + // Encrypt the response text using AES-GCM. + const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv); + let ciphertext = cipher.update(response.text, 'utf8', 'base64'); + ciphertext += cipher.final('base64'); + const authTag = cipher.getAuthTag().toString('base64'); + + // Encrypt the AES key using the client's RSA public key. + let encryptedKey; + try { + encryptedKey = crypto.publicEncrypt( + { + key: pemPublicKey, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: 'sha256', + }, + aesKey, + ).toString('base64'); + } catch (err) { + logger.error('Error encrypting AES key:', err); + throw new Error('Encryption failure'); + } + + // Replace the plaintext response with the encrypted payload. + response.text = ciphertext; + response.iv = iv.toString('base64'); + response.authTag = authTag; + response.encryptedKey = encryptedKey; + logger.debug('[AskController] Response message encrypted.'); + } catch (encError) { + logger.error('[AskController] Error during response encryption:', encError); + // Optionally, you may choose to return plaintext if encryption fails. + } + } + // --- End Encryption Branch --- + if (!abortController.signal.aborted) { sendMessage(res, { final: true, diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index 084d35cd6e..9238bda941 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -167,17 +167,20 @@ const updateUserEncryptionController = async (req, res) => { try { const { encryptionPublicKey, encryptedPrivateKey, encryptionSalt, encryptionIV } = req.body; - // Validate required parameters - if (!encryptionPublicKey || !encryptedPrivateKey || !encryptionSalt || !encryptionIV) { + // Allow disabling encryption by passing null for all fields. + const allNull = encryptionPublicKey === null && encryptedPrivateKey === null && encryptionSalt === null && encryptionIV === null; + const allPresent = encryptionPublicKey && encryptedPrivateKey && encryptionSalt && encryptionIV; + + if (!allNull && !allPresent) { return res.status(400).json({ message: 'Missing encryption parameters.' }); } - // Use the helper function to update the user. + // Update the user record with the provided encryption parameters (or null to disable) const updatedUser = await updateUser(req.user.id, { - encryptionPublicKey, - encryptedPrivateKey, - encryptionSalt, - encryptionIV, + encryptionPublicKey: encryptionPublicKey || null, + encryptedPrivateKey: encryptedPrivateKey || null, + encryptionSalt: encryptionSalt || null, + encryptionIV: encryptionIV || null, }); if (!updatedUser) { diff --git a/client/src/components/Chat/Messages/Content/MessageContent.tsx b/client/src/components/Chat/Messages/Content/MessageContent.tsx index 1547a01d80..433748ce1f 100644 --- a/client/src/components/Chat/Messages/Content/MessageContent.tsx +++ b/client/src/components/Chat/Messages/Content/MessageContent.tsx @@ -1,4 +1,4 @@ -import { memo, Suspense, useMemo } from 'react'; +import React, { memo, Suspense, useMemo, useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import type { TMessage } from 'librechat-data-provider'; import type { TMessageContentProps, TDisplayProps } from '~/common'; @@ -13,6 +13,77 @@ import Container from './Container'; import Markdown from './Markdown'; import { cn } from '~/utils'; import store from '~/store'; +import { useAuthContext } from '~/hooks/AuthContext'; + +/** + * Helper: Converts a base64 string to an ArrayBuffer. + */ +const base64ToArrayBuffer = (base64: string): ArrayBuffer => { + const binaryStr = window.atob(base64); + const len = binaryStr.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryStr.charCodeAt(i); + } + return bytes.buffer; +}; + +/** + * Helper: Decrypts an encrypted chat message using the provided RSA private key. + * Expects the message object to have: text (ciphertext), iv, authTag, and encryptedKey. + */ +async function decryptChatMessage( + msg: { text: string; iv: string; authTag: string; encryptedKey: string }, + privateKey: CryptoKey +): Promise { + // Convert base64 values to ArrayBuffers. + const ciphertextBuffer = base64ToArrayBuffer(msg.text); + const ivBuffer = new Uint8Array(base64ToArrayBuffer(msg.iv)); + const authTagBuffer = new Uint8Array(base64ToArrayBuffer(msg.authTag)); + const encryptedKeyBuffer = base64ToArrayBuffer(msg.encryptedKey); + + // Decrypt the AES key using RSA-OAEP. + let aesKeyRaw: ArrayBuffer; + try { + aesKeyRaw = await window.crypto.subtle.decrypt( + { name: 'RSA-OAEP' }, + privateKey, + encryptedKeyBuffer + ); + } catch (err) { + console.error('Failed to decrypt AES key:', err); + throw err; + } + + // Import the AES key. + const aesKey = await window.crypto.subtle.importKey( + 'raw', + aesKeyRaw, + { name: 'AES-GCM' }, + false, + ['decrypt'] + ); + + // Combine ciphertext and auth tag (Web Crypto expects them appended). + const ciphertextBytes = new Uint8Array(ciphertextBuffer); + const combined = new Uint8Array(ciphertextBytes.length + authTagBuffer.length); + combined.set(ciphertextBytes); + combined.set(authTagBuffer, ciphertextBytes.length); + + // Decrypt the message using AES-GCM. + let decryptedBuffer: ArrayBuffer; + try { + decryptedBuffer = await window.crypto.subtle.decrypt( + { name: 'AES-GCM', iv: ivBuffer }, + aesKey, + combined.buffer + ); + } catch (err) { + console.error('Failed to decrypt message:', err); + throw err; + } + return new TextDecoder().decode(decryptedBuffer); +} export const ErrorMessage = ({ text, @@ -40,12 +111,7 @@ export const ErrorMessage = ({ > -
+
{localize('com_ui_error_connection')}
@@ -58,10 +124,7 @@ export const ErrorMessage = ({
@@ -69,41 +132,65 @@ export const ErrorMessage = ({ ); }; -const DisplayMessage = ({ text, isCreatedByUser, message, showCursor }: TDisplayProps) => { +const DisplayMessage = ({ text, isCreatedByUser, message, showCursor, className = '' }: TDisplayProps) => { const { isSubmitting, latestMessage } = useChatContext(); + const { user } = useAuthContext(); const enableUserMsgMarkdown = useRecoilValue(store.enableUserMsgMarkdown); - const showCursorState = useMemo( - () => showCursor === true && isSubmitting, - [showCursor, isSubmitting], - ); - const isLatestMessage = useMemo( - () => message.messageId === latestMessage?.messageId, - [message.messageId, latestMessage?.messageId], - ); + const showCursorState = useMemo(() => showCursor === true && isSubmitting, [showCursor, isSubmitting]); + const isLatestMessage = useMemo(() => message.messageId === latestMessage?.messageId, [message.messageId, latestMessage?.messageId]); + + // State to hold the final text to display (decrypted if needed) + const [displayText, setDisplayText] = useState(text); + const [decryptionError, setDecryptionError] = useState(null); + + useEffect(() => { + if (message.encryptedKey && user?.decryptedPrivateKey) { + // Attempt to decrypt the message using our helper. + decryptChatMessage( + { + text: message.text, + iv: message.iv, + authTag: message.authTag, + encryptedKey: message.encryptedKey, + }, + user.decryptedPrivateKey + ) + .then((plainText) => { + setDisplayText(plainText); + setDecryptionError(null); + }) + .catch((err) => { + console.error('Error decrypting message:', err); + setDecryptionError('Decryption error'); + setDisplayText(''); + }); + } else { + // If no encryption metadata or no private key, display plain text. + setDisplayText(text); + setDecryptionError(null); + } + }, [text, message, user]); let content: React.ReactElement; if (!isCreatedByUser) { - content = ( - - ); + content = ; } else if (enableUserMsgMarkdown) { - content = ; + content = ; } else { - content = <>{text}; + content = <>{displayText}; } return ( -
- {content} +
+ {decryptionError ? {decryptionError} : content}
); @@ -162,15 +249,10 @@ const MessageContent = ({ {thinkingContent.length > 0 && ( {thinkingContent} )} - + {unfinishedMessage} ); }; -export default memo(MessageContent); +export default memo(MessageContent); \ No newline at end of file diff --git a/client/src/components/Nav/SettingsTabs/Chat/EncryptionPassphrase.tsx b/client/src/components/Nav/SettingsTabs/Chat/EncryptionPassphrase.tsx index 85ad8cbd05..53f12227ea 100644 --- a/client/src/components/Nav/SettingsTabs/Chat/EncryptionPassphrase.tsx +++ b/client/src/components/Nav/SettingsTabs/Chat/EncryptionPassphrase.tsx @@ -15,24 +15,17 @@ import type { TUser } from 'librechat-data-provider'; import { useToastContext } from '~/Providers'; import { useSetUserEncryptionMutation } from '~/data-provider'; -// Helper: Convert a Base64 string to Uint8Array. -const base64ToUint8Array = (base64: string): Uint8Array => { - const binaryString = window.atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; -}; - -// Helper: Convert a Uint8Array to a hex string (for debugging). +/** + * Helper: Convert a Uint8Array to a hex string (for debugging). + */ const uint8ArrayToHex = (array: Uint8Array): string => Array.from(array) - .map(b => b.toString(16).padStart(2, '0')) + .map((b) => b.toString(16).padStart(2, '0')) .join(''); -// Derive an AES-GCM key from the passphrase using PBKDF2. +/** + * Derive an AES-GCM key from the passphrase using PBKDF2. + */ const deriveKey = async (passphrase: string, salt: Uint8Array): Promise => { const encoder = new TextEncoder(); const keyMaterial = await window.crypto.subtle.importKey( @@ -46,7 +39,7 @@ const deriveKey = async (passphrase: string, salt: Uint8Array): Promise => { - const ciphertext = base64ToUint8Array(ciphertextBase64); - const decryptedBuffer = await window.crypto.subtle.decrypt( - { name: 'AES-GCM', iv }, - derivedKey, - ciphertext - ); - const decoder = new TextDecoder(); - return decoder.decode(decryptedBuffer); -}; - const UserKeysSettings: FC = () => { const localize = useLocalize(); const { user } = useAuthContext(); @@ -92,7 +69,15 @@ const UserKeysSettings: FC = () => { }, }); - // Activation/Update flow: Generate new keys and update user encryption fields. + /** + * Activation flow: + * 1. Generate a new RSA-OAEP key pair. + * 2. Export the public and private keys. + * 3. Generate a random salt (16 bytes) and IV (12 bytes) for AES-GCM. + * 4. Derive a symmetric key from the passphrase using PBKDF2. + * 5. Encrypt the private key with AES-GCM. + * 6. Return the base64-encoded encryption fields. + */ const activateEncryption = async (): Promise<{ encryptionPublicKey: string; encryptedPrivateKey: string; @@ -122,22 +107,23 @@ const UserKeysSettings: FC = () => { true, ['encrypt', 'decrypt'] ); - // Export public and private keys. + + // Export the public and private keys. const publicKeyBuffer = await window.crypto.subtle.exportKey('spki', keyPair.publicKey); const privateKeyBuffer = await window.crypto.subtle.exportKey('pkcs8', keyPair.privateKey); - const publicKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer))); - const privateKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer))); + const publicKeyBase64 = window.btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer))); + const privateKeyBase64 = window.btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer))); console.debug('New public key:', publicKeyBase64); console.debug('New private key (plaintext):', privateKeyBase64); - // Generate a salt and IV. - const salt = window.crypto.getRandomValues(new Uint8Array(16)); // 16 bytes salt - const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 12 bytes IV + // Generate a salt (16 bytes) and IV (12 bytes) for AES-GCM. + const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const iv = window.crypto.getRandomValues(new Uint8Array(12)); - // Derive a symmetric key from the passphrase. + // Derive a symmetric key from the passphrase using PBKDF2. const derivedKey = await deriveKey(passphrase, salt); - // Encrypt the private key. + // Encrypt the private key using AES-GCM. const encoder = new TextEncoder(); const privateKeyBytes = encoder.encode(privateKeyBase64); const encryptedPrivateKeyBuffer = await window.crypto.subtle.encrypt( @@ -145,11 +131,11 @@ const UserKeysSettings: FC = () => { derivedKey, privateKeyBytes ); - const encryptedPrivateKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(encryptedPrivateKeyBuffer))); + const encryptedPrivateKeyBase64 = window.btoa(String.fromCharCode(...new Uint8Array(encryptedPrivateKeyBuffer))); - // Convert salt and IV to Base64. - const saltBase64 = btoa(String.fromCharCode(...salt)); - const ivBase64 = btoa(String.fromCharCode(...iv)); + // Convert salt and IV to Base64 strings. + const saltBase64 = window.btoa(String.fromCharCode(...salt)); + const ivBase64 = window.btoa(String.fromCharCode(...iv)); console.debug('Activation complete:'); console.debug('Encrypted private key:', encryptedPrivateKeyBase64); @@ -167,12 +153,36 @@ const UserKeysSettings: FC = () => { } }; + /** + * Disable encryption flow: update the encryption fields to null. + */ + const disableEncryption = async (): Promise => { + try { + await setEncryption({ + encryptionPublicKey: null, + encryptedPrivateKey: null, + encryptionSalt: null, + encryptionIV: null, + }); + showToast({ message: localize('com_ui_upload_success') }); + // Update local user state with null encryption fields. + setUser((prev) => ({ + ...prev, + encryptionPublicKey: null, + encryptedPrivateKey: null, + encryptionSalt: null, + encryptionIV: null, + }) as TUser); + } catch (error) { + console.error('Error disabling encryption:', error); + } + }; + const handleSubmit = async (): Promise => { - // Activate encryption (or re-activate) by generating new keys. const newEncryption = await activateEncryption(); if (newEncryption) { try { - // Call the mutation to update the backend. + // Call the mutation to update the backend with new encryption fields. await setEncryption(newEncryption); showToast({ message: localize('com_ui_upload_success') }); // Update local user state with the new encryption keys. @@ -198,19 +208,29 @@ const UserKeysSettings: FC = () => {
- - {localize('com_nav_chat_encryption_settings')} - + {localize('com_nav_chat_encryption_settings')} +
+
+ + {user?.encryptionPublicKey && ( + + )}
-
{/* Optionally display current public key */} @@ -224,9 +244,7 @@ const UserKeysSettings: FC = () => { - - {localize('com_nav_chat_enter_your_passphrase')} - + {localize('com_nav_chat_enter_your_passphrase')}
& { - expiresAt: number; - } & TAttachmentMetadata); + expiresAt: number; +} & TAttachmentMetadata); export type TMessage = z.input & { children?: TMessage[]; @@ -780,11 +783,11 @@ export const googleSchema = tConversationSchema })); /** - * TODO: Map the following fields: - - presence_penalty -> presencePenalty - - frequency_penalty -> frequencyPenalty - - stop -> stopSequences - */ + * TODO: Map the following fields: + - presence_penalty -> presencePenalty + - frequency_penalty -> frequencyPenalty + - stop -> stopSequences + */ export const googleGenConfigSchema = z .object({ maxOutputTokens: coerceNumber.optional(), diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 30cade75e6..8c035c4d0a 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -41,11 +41,11 @@ export type TEndpointOption = { export type TPayload = Partial & Partial & { - isContinued: boolean; - conversationId: string | null; - messages?: TMessages; - isTemporary: boolean; - }; + isContinued: boolean; + conversationId: string | null; + messages?: TMessages; + isTemporary: boolean; +}; export type TSubmission = { artifacts?: string; @@ -111,10 +111,6 @@ export type TUser = { plugins?: string[]; createdAt: string; updatedAt: string; - encryptionPublicKey?: string; - encryptedPrivateKey?: string; // Encrypted as a Base64 string - encryptionSalt?: string; // Base64 encoded salt used for PBKDF2 - encryptionIV?: string; // Base64 encoded IV for AES-GCM }; export type TGetConversationsResponse = { @@ -482,10 +478,10 @@ export type TBannerResponse = TBanner | null; * Request type for updating user encryption keys. */ export type UpdateUserEncryptionRequest = { - encryptionPublicKey: string; - encryptedPrivateKey: string; - encryptionSalt: string; - encryptionIV: string; + encryptionPublicKey: string | null; + encryptedPrivateKey: string | null; + encryptionSalt: string | null; + encryptionIV: string | null; }; /**