feat: started with proper E2EE ;)

This commit is contained in:
Ruben Talstra 2025-02-15 21:26:40 +01:00
parent e3b5c59949
commit 18d019d8b3
No known key found for this signature in database
GPG key ID: 2A5A7174A60F3BEA
13 changed files with 380 additions and 1 deletions

View file

@ -54,6 +54,11 @@ 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,
},

View file

@ -23,6 +23,13 @@ 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 },
);

View file

@ -27,6 +27,10 @@ const { SystemRoles } = require('librechat-data-provider');
* @property {Array} [plugins=[]] - List of plugins used by the user
* @property {Array.<MongoSession>} [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 {Date} [createdAt] - Date when the user was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps)
*/
@ -132,6 +136,27 @@ const userSchema = mongoose.Schema(
type: Boolean,
default: false,
},
// --- New Fields for E2EE ---
encryptionPublicKey: {
type: String,
required: false,
// Provided by the client after key generation.
},
encryptedPrivateKey: {
type: String,
required: false,
// The private key encrypted on the client with the users encryption passphrase.
},
encryptionSalt: {
type: String,
required: false,
// Salt used for PBKDF2 when encrypting the private key.
},
encryptionIV: {
type: String,
required: false,
// IV used for AES-GCM encryption of the private key.
},
},
{ timestamps: true },

View file

@ -7,6 +7,7 @@ const {
deleteMessages,
deleteUserById,
deleteAllUserSessions,
updateUser,
} = require('~/models');
const User = require('~/models/User');
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
@ -162,6 +163,34 @@ const resendVerificationController = async (req, res) => {
}
};
const updateUserEncryptionController = async (req, res) => {
try {
const { encryptionPublicKey, encryptedPrivateKey, encryptionSalt, encryptionIV } = req.body;
// Validate required parameters
if (!encryptionPublicKey || !encryptedPrivateKey || !encryptionSalt || !encryptionIV) {
return res.status(400).json({ message: 'Missing encryption parameters.' });
}
// Use the helper function to update the user.
const updatedUser = await updateUser(req.user.id, {
encryptionPublicKey,
encryptedPrivateKey,
encryptionSalt,
encryptionIV,
});
if (!updatedUser) {
return res.status(404).json({ message: 'User not found.' });
}
res.status(200).json({ success: true });
} catch (error) {
logger.error('[updateUserEncryptionController]', error);
res.status(500).json({ message: 'Something went wrong updating encryption keys.' });
}
};
module.exports = {
getUserController,
getTermsStatusController,
@ -170,4 +199,5 @@ module.exports = {
verifyEmailController,
updateUserPluginsController,
resendVerificationController,
updateUserEncryptionController,
};

View file

@ -8,12 +8,14 @@ const {
resendVerificationController,
getTermsStatusController,
acceptTermsController,
updateUserEncryptionController,
} = require('~/server/controllers/UserController');
const router = express.Router();
router.get('/', requireJwtAuth, getUserController);
router.get('/terms', requireJwtAuth, getTermsStatusController);
router.put('/encryption', requireJwtAuth, updateUserEncryptionController);
router.post('/terms/accept', requireJwtAuth, acceptTermsController);
router.post('/plugins', requireJwtAuth, updateUserPluginsController);
router.delete('/delete', requireJwtAuth, canDeleteAccount, deleteUserController);