Переработано окно эндпоинта, добавлены элементы дебага, добавлена возможность сохранять и загружать конфигурацию эндпоинта, добавлено отображение ошибок при загрузке конфигурации. Исправлены мелкие баги.
This commit is contained in:
247
backend/src/services/IsolatedScriptExecutor.ts
Normal file
247
backend/src/services/IsolatedScriptExecutor.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import * as vm from 'vm';
|
||||
import { sqlExecutor } from './SqlExecutor';
|
||||
import { aqlExecutor } from './AqlExecutor';
|
||||
import { ScriptQuery, EndpointParameter, LogEntry, QueryExecution, IsolatedExecutionResult } from '../types';
|
||||
import { databasePoolManager } from './DatabasePoolManager';
|
||||
|
||||
interface IsolatedScriptContext {
|
||||
databaseId: string;
|
||||
scriptQueries: ScriptQuery[];
|
||||
requestParams: Record<string, any>;
|
||||
endpointParameters: EndpointParameter[];
|
||||
}
|
||||
|
||||
export class IsolatedScriptExecutor {
|
||||
private readonly TIMEOUT_MS = 600000; // 10 minutes
|
||||
|
||||
async execute(code: string, context: IsolatedScriptContext): Promise<IsolatedExecutionResult> {
|
||||
const logs: LogEntry[] = [];
|
||||
const queries: QueryExecution[] = [];
|
||||
|
||||
// Build captured console proxy
|
||||
const capturedConsole = {
|
||||
log: (...args: any[]) => {
|
||||
logs.push({ type: 'log', message: args.map(a => this.stringify(a)).join(' '), timestamp: Date.now() });
|
||||
},
|
||||
error: (...args: any[]) => {
|
||||
logs.push({ type: 'error', message: args.map(a => this.stringify(a)).join(' '), timestamp: Date.now() });
|
||||
},
|
||||
warn: (...args: any[]) => {
|
||||
logs.push({ type: 'warn', message: args.map(a => this.stringify(a)).join(' '), timestamp: Date.now() });
|
||||
},
|
||||
info: (...args: any[]) => {
|
||||
logs.push({ type: 'info', message: args.map(a => this.stringify(a)).join(' '), timestamp: Date.now() });
|
||||
},
|
||||
};
|
||||
|
||||
// Build execQuery function with tracking
|
||||
const execQuery = async (queryName: string, additionalParams: Record<string, any> = {}) => {
|
||||
const startTime = Date.now();
|
||||
const query = context.scriptQueries.find(q => q.name === queryName);
|
||||
|
||||
if (!query) {
|
||||
const entry: QueryExecution = {
|
||||
name: queryName,
|
||||
executionTime: Date.now() - startTime,
|
||||
success: false,
|
||||
error: `Query '${queryName}' not found`,
|
||||
};
|
||||
queries.push(entry);
|
||||
throw new Error(`Query '${queryName}' not found`);
|
||||
}
|
||||
|
||||
const allParams = { ...context.requestParams, ...additionalParams };
|
||||
const dbId = (query as any).database_id || context.databaseId;
|
||||
|
||||
if (!dbId) {
|
||||
const errMsg = `Database ID not found for query '${queryName}'. Please specify database_id in the Script Queries configuration.`;
|
||||
queries.push({ name: queryName, executionTime: Date.now() - startTime, success: false, error: errMsg });
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
const dbConfig = await databasePoolManager.getDatabaseConfig(dbId);
|
||||
if (!dbConfig) {
|
||||
const errMsg = `Database configuration not found for ID: ${dbId}`;
|
||||
queries.push({ name: queryName, executionTime: Date.now() - startTime, success: false, error: errMsg });
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
if (dbConfig.type === 'aql') {
|
||||
try {
|
||||
const result = await aqlExecutor.executeAqlQuery(dbId, {
|
||||
method: query.aql_method || 'GET',
|
||||
endpoint: query.aql_endpoint || '',
|
||||
body: query.aql_body || '',
|
||||
queryParams: query.aql_query_params || {},
|
||||
parameters: allParams,
|
||||
});
|
||||
|
||||
queries.push({
|
||||
name: queryName,
|
||||
executionTime: Date.now() - startTime,
|
||||
rowCount: result.rowCount,
|
||||
success: true,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
executionTime: result.executionTime,
|
||||
};
|
||||
} catch (error: any) {
|
||||
queries.push({
|
||||
name: queryName,
|
||||
executionTime: Date.now() - startTime,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
return { success: false, error: error.message, data: [], rowCount: 0 };
|
||||
}
|
||||
} else {
|
||||
if (!query.sql) {
|
||||
const errMsg = `SQL query is required for database '${dbConfig.name}' (type: ${dbConfig.type})`;
|
||||
queries.push({ name: queryName, executionTime: Date.now() - startTime, success: false, error: errMsg });
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
try {
|
||||
let processedQuery = query.sql;
|
||||
const paramValues: any[] = [];
|
||||
const paramMatches = query.sql.match(/\$\w+/g) || [];
|
||||
const uniqueParams = [...new Set(paramMatches.map(p => p.substring(1)))];
|
||||
|
||||
uniqueParams.forEach((paramName, index) => {
|
||||
const regex = new RegExp(`\\$${paramName}\\b`, 'g');
|
||||
processedQuery = processedQuery.replace(regex, `$${index + 1}`);
|
||||
const value = allParams[paramName];
|
||||
paramValues.push(value !== undefined ? value : null);
|
||||
});
|
||||
|
||||
const result = await sqlExecutor.executeQuery(dbId, processedQuery, paramValues);
|
||||
|
||||
queries.push({
|
||||
name: queryName,
|
||||
sql: query.sql,
|
||||
executionTime: Date.now() - startTime,
|
||||
rowCount: result.rowCount,
|
||||
success: true,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
executionTime: result.executionTime,
|
||||
};
|
||||
} catch (error: any) {
|
||||
queries.push({
|
||||
name: queryName,
|
||||
sql: query.sql,
|
||||
executionTime: Date.now() - startTime,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
return { success: false, error: error.message, data: [], rowCount: 0 };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create sandbox with null-prototype base
|
||||
const sandbox = Object.create(null);
|
||||
sandbox.params = context.requestParams;
|
||||
sandbox.console = capturedConsole;
|
||||
sandbox.execQuery = execQuery;
|
||||
|
||||
// Safe globals
|
||||
sandbox.JSON = JSON;
|
||||
sandbox.Date = Date;
|
||||
sandbox.Math = Math;
|
||||
sandbox.parseInt = parseInt;
|
||||
sandbox.parseFloat = parseFloat;
|
||||
sandbox.Array = Array;
|
||||
sandbox.Object = Object;
|
||||
sandbox.String = String;
|
||||
sandbox.Number = Number;
|
||||
sandbox.Boolean = Boolean;
|
||||
sandbox.RegExp = RegExp;
|
||||
sandbox.Map = Map;
|
||||
sandbox.Set = Set;
|
||||
sandbox.Promise = Promise;
|
||||
sandbox.Error = Error;
|
||||
sandbox.TypeError = TypeError;
|
||||
sandbox.RangeError = RangeError;
|
||||
sandbox.SyntaxError = SyntaxError;
|
||||
sandbox.isNaN = isNaN;
|
||||
sandbox.isFinite = isFinite;
|
||||
sandbox.undefined = undefined;
|
||||
sandbox.NaN = NaN;
|
||||
sandbox.Infinity = Infinity;
|
||||
sandbox.encodeURIComponent = encodeURIComponent;
|
||||
sandbox.decodeURIComponent = decodeURIComponent;
|
||||
sandbox.encodeURI = encodeURI;
|
||||
sandbox.decodeURI = decodeURI;
|
||||
|
||||
// Capped setTimeout/clearTimeout
|
||||
const timerIds = new Set<ReturnType<typeof setTimeout>>();
|
||||
sandbox.setTimeout = (fn: Function, ms: number, ...args: any[]) => {
|
||||
const cappedMs = Math.min(ms || 0, 30000);
|
||||
const id = setTimeout(() => {
|
||||
timerIds.delete(id);
|
||||
fn(...args);
|
||||
}, cappedMs);
|
||||
timerIds.add(id);
|
||||
return id;
|
||||
};
|
||||
sandbox.clearTimeout = (id: ReturnType<typeof setTimeout>) => {
|
||||
timerIds.delete(id);
|
||||
clearTimeout(id);
|
||||
};
|
||||
|
||||
const vmContext = vm.createContext(sandbox);
|
||||
|
||||
// Wrap user code in async IIFE
|
||||
const wrappedCode = `(async function() { ${code} })()`;
|
||||
|
||||
try {
|
||||
const script = new vm.Script(wrappedCode, { filename: 'user-script.js' });
|
||||
const resultPromise = script.runInContext(vmContext);
|
||||
|
||||
// Race against timeout
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Script execution timeout (10min)')), this.TIMEOUT_MS);
|
||||
});
|
||||
|
||||
const result = await Promise.race([resultPromise, timeoutPromise]);
|
||||
|
||||
// Clean up timers
|
||||
for (const id of timerIds) {
|
||||
clearTimeout(id);
|
||||
}
|
||||
timerIds.clear();
|
||||
|
||||
return { result, logs, queries };
|
||||
} catch (error: any) {
|
||||
// Clean up timers
|
||||
for (const id of timerIds) {
|
||||
clearTimeout(id);
|
||||
}
|
||||
timerIds.clear();
|
||||
|
||||
throw new Error(`JavaScript execution error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private stringify(value: any): string {
|
||||
if (value === null) return 'null';
|
||||
if (value === undefined) return 'undefined';
|
||||
if (typeof value === 'string') return value;
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isolatedScriptExecutor = new IsolatedScriptExecutor();
|
||||
@@ -1,8 +1,9 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { sqlExecutor } from './SqlExecutor';
|
||||
import { aqlExecutor } from './AqlExecutor';
|
||||
import { ScriptQuery, EndpointParameter } from '../types';
|
||||
import { ScriptQuery, EndpointParameter, LogEntry, QueryExecution, IsolatedExecutionResult } from '../types';
|
||||
import { databasePoolManager } from './DatabasePoolManager';
|
||||
import { isolatedScriptExecutor } from './IsolatedScriptExecutor';
|
||||
|
||||
interface ScriptContext {
|
||||
databaseId: string;
|
||||
@@ -13,122 +14,19 @@ interface ScriptContext {
|
||||
|
||||
export class ScriptExecutor {
|
||||
/**
|
||||
* Выполняет JavaScript скрипт
|
||||
* Выполняет JavaScript скрипт через изолированный VM контекст
|
||||
*/
|
||||
async executeJavaScript(code: string, context: ScriptContext): Promise<any> {
|
||||
try {
|
||||
// Создаем функцию execQuery, доступную в скрипте
|
||||
const execQuery = async (queryName: string, additionalParams: Record<string, any> = {}) => {
|
||||
const query = context.scriptQueries.find(q => q.name === queryName);
|
||||
if (!query) {
|
||||
throw new Error(`Query '${queryName}' not found`);
|
||||
}
|
||||
|
||||
const allParams = { ...context.requestParams, ...additionalParams };
|
||||
const dbId = (query as any).database_id || context.databaseId;
|
||||
|
||||
if (!dbId) {
|
||||
throw new Error(`Database ID not found for query '${queryName}'. Query database_id: ${(query as any).database_id}, Context databaseId: ${context.databaseId}. Please specify database_id in the Script Queries configuration for query '${queryName}'.`);
|
||||
}
|
||||
|
||||
// Получаем конфигурацию базы данных для определения типа
|
||||
const dbConfig = await databasePoolManager.getDatabaseConfig(dbId);
|
||||
if (!dbConfig) {
|
||||
throw new Error(`Database configuration not found for ID: ${dbId}`);
|
||||
}
|
||||
|
||||
// Проверяем тип базы данных и выполняем соответствующий запрос
|
||||
if (dbConfig.type === 'aql') {
|
||||
// AQL запрос
|
||||
try {
|
||||
const result = await aqlExecutor.executeAqlQuery(dbId, {
|
||||
method: query.aql_method || 'GET',
|
||||
endpoint: query.aql_endpoint || '',
|
||||
body: query.aql_body || '',
|
||||
queryParams: query.aql_query_params || {},
|
||||
parameters: allParams,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
executionTime: result.executionTime,
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Возвращаем ошибку как объект, а не бросаем исключение
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
data: [],
|
||||
rowCount: 0,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// SQL запрос
|
||||
if (!query.sql) {
|
||||
throw new Error(`SQL query is required for database '${dbConfig.name}' (type: ${dbConfig.type})`);
|
||||
}
|
||||
|
||||
try {
|
||||
let processedQuery = query.sql;
|
||||
const paramValues: any[] = [];
|
||||
const paramMatches = query.sql.match(/\$\w+/g) || [];
|
||||
const uniqueParams = [...new Set(paramMatches.map(p => p.substring(1)))];
|
||||
|
||||
uniqueParams.forEach((paramName, index) => {
|
||||
const regex = new RegExp(`\\$${paramName}\\b`, 'g');
|
||||
processedQuery = processedQuery.replace(regex, `$${index + 1}`);
|
||||
const value = allParams[paramName];
|
||||
paramValues.push(value !== undefined ? value : null);
|
||||
});
|
||||
|
||||
const result = await sqlExecutor.executeQuery(dbId, processedQuery, paramValues);
|
||||
|
||||
console.log(`[execQuery ${queryName}] success, rowCount:`, result.rowCount);
|
||||
return {
|
||||
success: true,
|
||||
data: result.rows,
|
||||
rowCount: result.rowCount,
|
||||
executionTime: result.executionTime,
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Возвращаем ошибку как объект, а не бросаем исключение
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
data: [],
|
||||
rowCount: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Создаем асинхронную функцию из кода пользователя
|
||||
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
||||
const userFunction = new AsyncFunction('params', 'execQuery', code);
|
||||
|
||||
// Устанавливаем таймаут (10 минут)
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Script execution timeout (10min)')), 600000);
|
||||
});
|
||||
|
||||
// Выполняем скрипт с таймаутом
|
||||
const result = await Promise.race([
|
||||
userFunction(context.requestParams, execQuery),
|
||||
timeoutPromise
|
||||
]);
|
||||
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
throw new Error(`JavaScript execution error: ${error.message}`);
|
||||
}
|
||||
async executeJavaScript(code: string, context: ScriptContext): Promise<IsolatedExecutionResult> {
|
||||
return isolatedScriptExecutor.execute(code, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет Python скрипт в отдельном процессе
|
||||
*/
|
||||
async executePython(code: string, context: ScriptContext): Promise<any> {
|
||||
async executePython(code: string, context: ScriptContext): Promise<IsolatedExecutionResult> {
|
||||
const logs: LogEntry[] = [];
|
||||
const queries: QueryExecution[] = [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Сериализуем параметры в JSON строку
|
||||
const paramsJson = JSON.stringify(context.requestParams);
|
||||
@@ -179,7 +77,6 @@ print(json.dumps(result))
|
||||
const python = spawn(pythonCommand, ['-c', wrapperCode]);
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
let queryRequests: any[] = [];
|
||||
|
||||
python.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
@@ -192,12 +89,19 @@ print(json.dumps(result))
|
||||
// Проверяем на запросы к БД
|
||||
const requestMatches = text.matchAll(/__QUERY_REQUEST__(.*?)__END_REQUEST__/g);
|
||||
for (const match of requestMatches) {
|
||||
const queryStartTime = Date.now();
|
||||
try {
|
||||
const request = JSON.parse(match[1]);
|
||||
|
||||
// Выполняем запрос
|
||||
const query = context.scriptQueries.find(q => q.name === request.query_name);
|
||||
if (!query) {
|
||||
queries.push({
|
||||
name: request.query_name,
|
||||
executionTime: Date.now() - queryStartTime,
|
||||
success: false,
|
||||
error: `Query '${request.query_name}' not found`,
|
||||
});
|
||||
python.stdin.write(JSON.stringify({ error: `Query '${request.query_name}' not found` }) + '\n');
|
||||
continue;
|
||||
}
|
||||
@@ -206,18 +110,18 @@ print(json.dumps(result))
|
||||
const dbId = (query as any).database_id || context.databaseId;
|
||||
|
||||
if (!dbId) {
|
||||
python.stdin.write(JSON.stringify({
|
||||
error: `Database ID not found for query '${request.query_name}'. Query database_id: ${(query as any).database_id}, Context databaseId: ${context.databaseId}. Please specify database_id in the Script Queries configuration for query '${request.query_name}'.`
|
||||
}) + '\n');
|
||||
const errMsg = `Database ID not found for query '${request.query_name}'.`;
|
||||
queries.push({ name: request.query_name, executionTime: Date.now() - queryStartTime, success: false, error: errMsg });
|
||||
python.stdin.write(JSON.stringify({ error: errMsg }) + '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Получаем конфигурацию базы данных для определения типа
|
||||
const dbConfig = await databasePoolManager.getDatabaseConfig(dbId);
|
||||
if (!dbConfig) {
|
||||
python.stdin.write(JSON.stringify({
|
||||
error: `Database configuration not found for ID: ${dbId}`
|
||||
}) + '\n');
|
||||
const errMsg = `Database configuration not found for ID: ${dbId}`;
|
||||
queries.push({ name: request.query_name, executionTime: Date.now() - queryStartTime, success: false, error: errMsg });
|
||||
python.stdin.write(JSON.stringify({ error: errMsg }) + '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -233,6 +137,13 @@ print(json.dumps(result))
|
||||
parameters: allParams,
|
||||
});
|
||||
|
||||
queries.push({
|
||||
name: request.query_name,
|
||||
executionTime: Date.now() - queryStartTime,
|
||||
rowCount: result.rowCount,
|
||||
success: true,
|
||||
});
|
||||
|
||||
python.stdin.write(JSON.stringify({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
@@ -240,7 +151,12 @@ print(json.dumps(result))
|
||||
executionTime: result.executionTime,
|
||||
}) + '\n');
|
||||
} catch (error: any) {
|
||||
// Отправляем ошибку как объект, а не через поле error
|
||||
queries.push({
|
||||
name: request.query_name,
|
||||
executionTime: Date.now() - queryStartTime,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
python.stdin.write(JSON.stringify({
|
||||
success: false,
|
||||
error: error.message,
|
||||
@@ -251,9 +167,11 @@ print(json.dumps(result))
|
||||
} else {
|
||||
// SQL запрос
|
||||
if (!query.sql) {
|
||||
const errMsg = `SQL query is required for database '${dbConfig.name}' (type: ${dbConfig.type})`;
|
||||
queries.push({ name: request.query_name, sql: query.sql, executionTime: Date.now() - queryStartTime, success: false, error: errMsg });
|
||||
python.stdin.write(JSON.stringify({
|
||||
success: false,
|
||||
error: `SQL query is required for database '${dbConfig.name}' (type: ${dbConfig.type})`,
|
||||
error: errMsg,
|
||||
data: [],
|
||||
rowCount: 0,
|
||||
}) + '\n');
|
||||
@@ -280,6 +198,14 @@ print(json.dumps(result))
|
||||
paramValues
|
||||
);
|
||||
|
||||
queries.push({
|
||||
name: request.query_name,
|
||||
sql: query.sql,
|
||||
executionTime: Date.now() - queryStartTime,
|
||||
rowCount: result.rowCount,
|
||||
success: true,
|
||||
});
|
||||
|
||||
python.stdin.write(JSON.stringify({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
@@ -287,6 +213,13 @@ print(json.dumps(result))
|
||||
executionTime: result.executionTime,
|
||||
}) + '\n');
|
||||
} catch (error: any) {
|
||||
queries.push({
|
||||
name: request.query_name,
|
||||
sql: query.sql,
|
||||
executionTime: Date.now() - queryStartTime,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
python.stdin.write(JSON.stringify({
|
||||
success: false,
|
||||
error: error.message,
|
||||
@@ -296,6 +229,12 @@ print(json.dumps(result))
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
queries.push({
|
||||
name: 'unknown',
|
||||
executionTime: Date.now() - queryStartTime,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
python.stdin.write(JSON.stringify({
|
||||
success: false,
|
||||
error: error.message,
|
||||
@@ -304,18 +243,38 @@ print(json.dumps(result))
|
||||
}) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Capture non-query stderr output as log entries
|
||||
const nonQueryLines = text.replace(/__QUERY_REQUEST__.*?__END_REQUEST__/g, '').trim();
|
||||
if (nonQueryLines) {
|
||||
nonQueryLines.split('\n').forEach((line: string) => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed) {
|
||||
logs.push({ type: 'log', message: trimmed, timestamp: Date.now() });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
python.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
python.on('close', (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
reject(new Error(`Python execution error: ${errorOutput}`));
|
||||
} else {
|
||||
try {
|
||||
// Последняя строка вывода - результат
|
||||
// Последняя строка вывода - результат, остальные - логи
|
||||
const lines = output.trim().split('\n');
|
||||
const resultLine = lines[lines.length - 1];
|
||||
|
||||
// Capture print() output lines (everything except the last JSON result)
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const trimmed = lines[i].trim();
|
||||
if (trimmed) {
|
||||
logs.push({ type: 'log', message: trimmed, timestamp: Date.now() });
|
||||
}
|
||||
}
|
||||
|
||||
const result = JSON.parse(resultLine);
|
||||
resolve(result);
|
||||
resolve({ result, logs, queries });
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to parse Python output: ${output}`));
|
||||
}
|
||||
@@ -337,7 +296,7 @@ print(json.dumps(result))
|
||||
language: 'javascript' | 'python',
|
||||
code: string,
|
||||
context: ScriptContext
|
||||
): Promise<any> {
|
||||
): Promise<IsolatedExecutionResult> {
|
||||
if (language === 'javascript') {
|
||||
return this.executeJavaScript(code, context);
|
||||
} else if (language === 'python') {
|
||||
|
||||
35
backend/src/services/endpointCrypto.ts
Normal file
35
backend/src/services/endpointCrypto.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
const ENCRYPTION_KEY = 'kis-api-builder-endpoint-key-32b'; // exactly 32 bytes for AES-256
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
|
||||
export function encryptEndpointData(data: object): Buffer {
|
||||
const json = JSON.stringify(data);
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'utf-8'), iv);
|
||||
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(json, 'utf8'),
|
||||
cipher.final(),
|
||||
]);
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// Format: [16 bytes IV][16 bytes authTag][...encrypted data]
|
||||
return Buffer.concat([iv, authTag, encrypted]);
|
||||
}
|
||||
|
||||
export function decryptEndpointData(buffer: Buffer): object {
|
||||
const iv = buffer.subarray(0, 16);
|
||||
const authTag = buffer.subarray(16, 32);
|
||||
const encrypted = buffer.subarray(32);
|
||||
|
||||
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'utf-8'), iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(encrypted),
|
||||
decipher.final(),
|
||||
]);
|
||||
|
||||
return JSON.parse(decrypted.toString('utf8'));
|
||||
}
|
||||
Reference in New Issue
Block a user