MCP: bloquear escritura de records por accessList del usuario
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
38
mcp-server/tools/helpers/accessControl.js
Normal file
38
mcp-server/tools/helpers/accessControl.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current user has write access to a table.
|
||||||
|
* Reads .acai file from ACAI_PROJECT_DIR.
|
||||||
|
* Returns { allowed: true } or { allowed: false, error: "..." }
|
||||||
|
*/
|
||||||
|
export function canAccessTable(tableName) {
|
||||||
|
const projectDir = process.env.ACAI_PROJECT_DIR || "";
|
||||||
|
if (!projectDir) return { allowed: true }; // no project dir, don't block
|
||||||
|
|
||||||
|
const acaiFile = path.join(projectDir, ".acai");
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(acaiFile)) return { allowed: true };
|
||||||
|
const data = JSON.parse(fs.readFileSync(acaiFile, "utf-8"));
|
||||||
|
const user = data.user || {};
|
||||||
|
|
||||||
|
// Admin has full access
|
||||||
|
if (user.isAdmin === "1" || user.isAdmin === 1) return { allowed: true };
|
||||||
|
|
||||||
|
const accessList = user.accessList || {};
|
||||||
|
if (!accessList || Object.keys(accessList).length === 0) return { allowed: true };
|
||||||
|
|
||||||
|
// all.accessLevel >= 9 means full access
|
||||||
|
const allAccess = parseInt(accessList.all?.accessLevel || "0");
|
||||||
|
if (allAccess >= 9) return { allowed: true };
|
||||||
|
|
||||||
|
// Check specific table (without cms_ prefix)
|
||||||
|
const bare = tableName.replace(/^cms_/, "");
|
||||||
|
const entry = accessList[bare];
|
||||||
|
if (entry && parseInt(entry.accessLevel || "0") > 0) return { allowed: true };
|
||||||
|
|
||||||
|
return { allowed: false, error: `No tienes acceso a la tabla '${bare}'` };
|
||||||
|
} catch (e) {
|
||||||
|
return { allowed: true }; // On error, don't block
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { withAuth, getSessionCredentials } from "../../auth/index.js";
|
|||||||
import { handleApiResponse, handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
import { handleApiResponse, handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
||||||
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
||||||
import { withAuthParams } from "../helpers/authSchema.js";
|
import { withAuthParams } from "../helpers/authSchema.js";
|
||||||
|
import { canAccessTable } from "../helpers/accessControl.js";
|
||||||
|
|
||||||
export function registerAddModuleToRecordTool(server) {
|
export function registerAddModuleToRecordTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -29,6 +30,12 @@ Response includes: sectionId, moduleId, position, totalModules`,
|
|||||||
const validationError = validateRequired({ tableName, recordNum, moduleId }, ['tableName', 'recordNum', 'moduleId'], 'add_module_to_record');
|
const validationError = validateRequired({ tableName, recordNum, moduleId }, ['tableName', 'recordNum', 'moduleId'], 'add_module_to_record');
|
||||||
if (validationError) return validationError;
|
if (validationError) return validationError;
|
||||||
|
|
||||||
|
// Check table access
|
||||||
|
const accessCheck = canAccessTable(tableName);
|
||||||
|
if (!accessCheck.allowed) {
|
||||||
|
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: accessCheck.error }) }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
const sessionId = extra.sessionId;
|
const sessionId = extra.sessionId;
|
||||||
const credentials = await getSessionCredentials(sessionId);
|
const credentials = await getSessionCredentials(sessionId);
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { handleToolError, validateRequired, handleApiResponse } from "../helpers
|
|||||||
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
||||||
import { table } from "console";
|
import { table } from "console";
|
||||||
import { withAuthParams } from "../helpers/authSchema.js";
|
import { withAuthParams } from "../helpers/authSchema.js";
|
||||||
|
import { canAccessTable } from "../helpers/accessControl.js";
|
||||||
|
|
||||||
export function registerCreateOrUpdateRecordTool(server) {
|
export function registerCreateOrUpdateRecordTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -26,6 +27,12 @@ export function registerCreateOrUpdateRecordTool(server) {
|
|||||||
const validationError = validateRequired({ tableName, fields }, ['tableName', 'fields'], 'create_or_update_record');
|
const validationError = validateRequired({ tableName, fields }, ['tableName', 'fields'], 'create_or_update_record');
|
||||||
if (validationError) return validationError;
|
if (validationError) return validationError;
|
||||||
|
|
||||||
|
// Check table access
|
||||||
|
const accessCheck = canAccessTable(tableName);
|
||||||
|
if (!accessCheck.allowed) {
|
||||||
|
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: accessCheck.error }) }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
// if fields is string, try to parse as JSON
|
// if fields is string, try to parse as JSON
|
||||||
if (typeof fields === 'string') {
|
if (typeof fields === 'string') {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { withAuth, getSessionCredentials } from "../../auth/index.js";
|
|||||||
import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js";
|
import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js";
|
||||||
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
import { AcaiHttpClient } from "../helpers/acaiHttpClient.js";
|
||||||
import { withAuthParams } from "../helpers/authSchema.js";
|
import { withAuthParams } from "../helpers/authSchema.js";
|
||||||
|
import { canAccessTable } from "../helpers/accessControl.js";
|
||||||
|
|
||||||
export function registerDeleteTableRecordsTool(server) {
|
export function registerDeleteTableRecordsTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -20,6 +21,12 @@ export function registerDeleteTableRecordsTool(server) {
|
|||||||
const validationError = validateRequired({ tableName }, ['tableName'], 'delete_table_records');
|
const validationError = validateRequired({ tableName }, ['tableName'], 'delete_table_records');
|
||||||
if (validationError) return validationError;
|
if (validationError) return validationError;
|
||||||
|
|
||||||
|
// Check table access
|
||||||
|
const accessCheck = canAccessTable(tableName);
|
||||||
|
if (!accessCheck.allowed) {
|
||||||
|
return { content: [{ type: "text", text: JSON.stringify({ success: false, error: accessCheck.error }) }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
if (!recordIds && !deleteAll) {
|
if (!recordIds && !deleteAll) {
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: "Error: You must provide either 'recordIds' or set 'deleteAll' to true." }],
|
content: [{ type: "text", text: "Error: You must provide either 'recordIds' or set 'deleteAll' to true." }],
|
||||||
|
|||||||
Reference in New Issue
Block a user