File size: 7,990 Bytes
f0743f4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | const { logger } = require('@librechat/data-schemas');
const { encrypt, decrypt } = require('@librechat/api');
const { ErrorTypes } = require('librechat-data-provider');
const { updateUser } = require('~/models');
const { Key } = require('~/db/models');
/**
* Updates the plugins for a user based on the action specified (install/uninstall).
* @async
* @param {Object} user - The user whose plugins are to be updated.
* @param {string} pluginKey - The key of the plugin to install or uninstall.
* @param {'install' | 'uninstall'} action - The action to perform, 'install' or 'uninstall'.
* @returns {Promise<Object>} The result of the update operation.
* @throws Logs the error internally if the update operation fails.
* @description This function updates the plugin array of a user document based on the specified action.
* It adds a plugin key to the plugins array for an 'install' action, and removes it for an 'uninstall' action.
*/
const updateUserPluginsService = async (user, pluginKey, action) => {
try {
const userPlugins = user.plugins || [];
if (action === 'install') {
return await updateUser(user._id, { plugins: [...userPlugins, pluginKey] });
} else if (action === 'uninstall') {
return await updateUser(user._id, {
plugins: userPlugins.filter((plugin) => plugin !== pluginKey),
});
}
} catch (err) {
logger.error('[updateUserPluginsService]', err);
return err;
}
};
/**
* Retrieves and decrypts the key value for a given user identified by userId and identifier name.
* @param {Object} params - The parameters object.
* @param {string} params.userId - The unique identifier for the user.
* @param {string} params.name - The name associated with the key.
* @returns {Promise<string>} The decrypted key value.
* @throws {Error} Throws an error if the key is not found or if there is a problem during key retrieval.
* @description This function searches for a user's key in the database using their userId and name.
* If found, it decrypts the value of the key and returns it. If no key is found, it throws
* an error indicating that there is no user key available.
*/
const getUserKey = async ({ userId, name }) => {
const keyValue = await Key.findOne({ userId, name }).lean();
if (!keyValue) {
throw new Error(
JSON.stringify({
type: ErrorTypes.NO_USER_KEY,
}),
);
}
return await decrypt(keyValue.value);
};
/**
* Retrieves, decrypts, and parses the key values for a given user identified by userId and name.
* @param {Object} params - The parameters object.
* @param {string} params.userId - The unique identifier for the user.
* @param {string} params.name - The name associated with the key.
* @returns {Promise<Record<string,string>>} The decrypted and parsed key values.
* @throws {Error} Throws an error if the key is invalid or if there is a problem during key value parsing.
* @description This function retrieves a user's encrypted key using their userId and name, decrypts it,
* and then attempts to parse the decrypted string into a JSON object. If the parsing fails,
* it throws an error indicating that the user key is invalid.
*/
const getUserKeyValues = async ({ userId, name }) => {
let userValues = await getUserKey({ userId, name });
try {
userValues = JSON.parse(userValues);
} catch (e) {
logger.error('[getUserKeyValues]', e);
throw new Error(
JSON.stringify({
type: ErrorTypes.INVALID_USER_KEY,
}),
);
}
return userValues;
};
/**
* Retrieves the expiry information of a user's key identified by userId and name.
* @async
* @param {Object} params - The parameters object.
* @param {string} params.userId - The unique identifier for the user.
* @param {string} params.name - The name associated with the key.
* @returns {Promise<{expiresAt: Date | null}>} The expiry date of the key or null if the key doesn't exist.
* @description This function fetches a user's key from the database using their userId and name and
* returns its expiry date. If the key is not found, it returns null for the expiry date.
*/
const getUserKeyExpiry = async ({ userId, name }) => {
const keyValue = await Key.findOne({ userId, name }).lean();
if (!keyValue) {
return { expiresAt: null };
}
return { expiresAt: keyValue.expiresAt || 'never' };
};
/**
* Updates or inserts a new key for a given user identified by userId and name, with a specified value and expiry date.
* @async
* @param {Object} params - The parameters object.
* @param {string} params.userId - The unique identifier for the user.
* @param {string} params.name - The name associated with the key.
* @param {string} params.value - The value to be encrypted and stored as the key's value.
* @param {Date} params.expiresAt - The expiry date for the key [optional]
* @returns {Promise<Object>} The updated or newly inserted key document.
* @description This function either updates an existing user key or inserts a new one into the database,
* after encrypting the provided value. It sets the provided expiry date for the key (or unsets for no expiry).
*/
const updateUserKey = async ({ userId, name, value, expiresAt = null }) => {
const encryptedValue = await encrypt(value);
let updateObject = {
userId,
name,
value: encryptedValue,
};
const updateQuery = { $set: updateObject };
// add expiresAt to the update object if it's not null
if (expiresAt) {
updateObject.expiresAt = new Date(expiresAt);
} else {
// make sure to remove if already present
updateQuery.$unset = { expiresAt };
}
return await Key.findOneAndUpdate({ userId, name }, updateQuery, {
upsert: true,
new: true,
}).lean();
};
/**
* Deletes a key or all keys for a given user identified by userId, optionally based on a specified name.
* @async
* @param {Object} params - The parameters object.
* @param {string} params.userId - The unique identifier for the user.
* @param {string} [params.name] - The name associated with the key to delete. If not provided and all is true, deletes all keys.
* @param {boolean} [params.all=false] - Whether to delete all keys for the user.
* @returns {Promise<Object>} The result of the deletion operation.
* @description This function deletes a specific key or all keys for a user from the database.
* If a name is provided and all is false, it deletes only the key with that name.
* If all is true, it ignores the name and deletes all keys for the user.
*/
const deleteUserKey = async ({ userId, name, all = false }) => {
if (all) {
return await Key.deleteMany({ userId });
}
await Key.findOneAndDelete({ userId, name }).lean();
};
/**
* Checks if a user key has expired based on the provided expiration date and endpoint.
* If the key has expired, it throws an Error with details including the type of error, the expiration date, and the endpoint.
*
* @param {string} expiresAt - The expiration date of the user key in a format that can be parsed by the Date constructor.
* @param {string} endpoint - The endpoint associated with the user key to be checked.
* @throws {Error} Throws an error if the user key has expired. The error message is a stringified JSON object
* containing the type of error (`ErrorTypes.EXPIRED_USER_KEY`), the expiration date in the local string format, and the endpoint.
*/
const checkUserKeyExpiry = (expiresAt, endpoint) => {
const expiresAtDate = new Date(expiresAt);
if (expiresAtDate < new Date()) {
const errorMessage = JSON.stringify({
type: ErrorTypes.EXPIRED_USER_KEY,
expiredAt: expiresAtDate.toLocaleString(),
endpoint,
});
throw new Error(errorMessage);
}
};
module.exports = {
getUserKey,
updateUserKey,
deleteUserKey,
getUserKeyValues,
getUserKeyExpiry,
checkUserKeyExpiry,
updateUserPluginsService,
};
|