Files
anythingllm/server/models/browserExtensionApiKey.js
Timothy Carambat a207449095 Enforce user suspension check on browser extension API key path
Previously, suspended users could continue using browser extension
endpoints if they had created an API key before suspension. The normal
JWT session path blocked suspended users, but the browser extension
middleware did not.

Changes:
- Add suspension and user existence checks to validBrowserExtensionApiKey
- Delete browser extension API keys when a user is deleted
- Add deleteAllForUser method to BrowserExtensionApiKey model
GHSA-7754-8jcc-2rg3
2026-03-13 10:05:05 -07:00

191 lines
5.7 KiB
JavaScript

const prisma = require("../utils/prisma");
const { SystemSettings } = require("./systemSettings");
const { ROLES } = require("../utils/middleware/multiUserProtected");
const BrowserExtensionApiKey = {
/**
* Creates a new secret for a browser extension API key.
* @returns {string} brx-*** API key to use with extension
*/
makeSecret: () => {
const uuidAPIKey = require("uuid-apikey");
return `brx-${uuidAPIKey.create().apiKey}`;
},
/**
* Creates a new api key for the browser Extension
* @param {number|null} userId - User id to associate creation of key with.
* @returns {Promise<{apiKey: import("@prisma/client").browser_extension_api_keys|null, error:string|null}>}
*/
create: async function (userId = null) {
try {
const apiKey = await prisma.browser_extension_api_keys.create({
data: {
key: this.makeSecret(),
user_id: userId,
},
});
return { apiKey, error: null };
} catch (error) {
console.error("Failed to create browser extension API key", error);
return { apiKey: null, error: error.message };
}
},
/**
* Validated existing API key
* @param {string} key
* @returns {Promise<{apiKey: import("@prisma/client").browser_extension_api_keys|boolean}>}
*/
validate: async function (key) {
if (!key.startsWith("brx-")) return false;
const apiKey = await prisma.browser_extension_api_keys.findUnique({
where: { key: key.toString() },
include: { user: true },
});
if (!apiKey) return false;
const multiUserMode = await SystemSettings.isMultiUserMode();
if (!multiUserMode) return apiKey; // In single-user mode, all keys are valid
// In multi-user mode, check if the key is associated with a user
return apiKey.user_id ? apiKey : false;
},
/**
* Fetches browser api key by params.
* @param {object} clause - Prisma props for search
* @returns {Promise<{apiKey: import("@prisma/client").browser_extension_api_keys|boolean}>}
*/
get: async function (clause = {}) {
try {
const apiKey = await prisma.browser_extension_api_keys.findFirst({
where: clause,
});
return apiKey;
} catch (error) {
console.error("FAILED TO GET BROWSER EXTENSION API KEY.", error.message);
return null;
}
},
/**
* Deletes browser api key by db id.
* @param {number} id - database id of browser key
* @returns {Promise<{success: boolean, error:string|null}>}
*/
delete: async function (id) {
try {
await prisma.browser_extension_api_keys.delete({
where: { id: parseInt(id) },
});
return { success: true, error: null };
} catch (error) {
console.error("Failed to delete browser extension API key", error);
return { success: false, error: error.message };
}
},
/**
* Deletes all browser extension API keys for a user.
* Should be called when a user is deleted to revoke all their keys.
* @param {number} userId - The user ID whose keys should be deleted
* @returns {Promise<{success: boolean, error: string|null}>}
*/
deleteAllForUser: async function (userId) {
try {
if (!userId) return { success: false, error: "User ID is required" };
await prisma.browser_extension_api_keys.deleteMany({
where: { user_id: parseInt(userId) },
});
return { success: true, error: null };
} catch (error) {
console.error(
"Failed to delete browser extension API keys for user",
error
);
return { success: false, error: error.message };
}
},
/**
* Gets browser keys by params
* @param {object} clause
* @param {number|null} limit
* @param {object|null} orderBy
* @returns {Promise<import("@prisma/client").browser_extension_api_keys[]>}
*/
where: async function (clause = {}, limit = null, orderBy = null) {
try {
const apiKeys = await prisma.browser_extension_api_keys.findMany({
where: clause,
...(limit !== null ? { take: limit } : {}),
...(orderBy !== null ? { orderBy } : {}),
include: { user: true },
});
return apiKeys;
} catch (error) {
console.error("FAILED TO GET BROWSER EXTENSION API KEYS.", error.message);
return [];
}
},
/**
* Get browser API keys for user
* @param {import("@prisma/client").users} user
* @param {object} clause
* @param {number|null} limit
* @param {object|null} orderBy
* @returns {Promise<import("@prisma/client").browser_extension_api_keys[]>}
*/
whereWithUser: async function (
user,
clause = {},
limit = null,
orderBy = null
) {
// Admin can view and use any keys
if ([ROLES.admin].includes(user.role))
return await this.where(clause, limit, orderBy);
try {
const apiKeys = await prisma.browser_extension_api_keys.findMany({
where: {
...clause,
user_id: user.id,
},
include: { user: true },
...(limit !== null ? { take: limit } : {}),
...(orderBy !== null ? { orderBy } : {}),
});
return apiKeys;
} catch (error) {
console.error(error.message);
return [];
}
},
/**
* Updates owner of all DB ids to new admin.
* @param {number} userId
* @returns {Promise<void>}
*/
migrateApiKeysToMultiUser: async function (userId) {
try {
await prisma.browser_extension_api_keys.updateMany({
where: {
user_id: null,
},
data: {
user_id: userId,
},
});
console.log("Successfully migrated API keys to multi-user mode");
} catch (error) {
console.error("Error migrating API keys to multi-user mode:", error);
}
},
};
module.exports = { BrowserExtensionApiKey };