Files
Final_PGA/backend/src/modules/sql-console/sql-console.service.ts
2026-03-19 18:00:46 +07:00

59 lines
1.9 KiB
TypeScript

import { createHash } from "node:crypto";
import { getTargetPool } from "../../db/target.js";
import { AppError } from "../../lib/errors.js";
import { guardSql } from "../../lib/sql-guard.js";
import type { SessionUser } from "../../types/auth.js";
import { createAuditEvent } from "../audit/audit.service.js";
function isReadOnly(user: SessionUser) {
if (user.isRoot) {
return false;
}
const hasWrite = user.permissions.some((grant) => grant.action === "write" || grant.action === "schema_change");
return !hasWrite;
}
export async function executeSql(sql: string, user: SessionUser, context: { ip?: string; userAgent?: string }) {
if (process.env.FEATURE_SQL_CONSOLE === "false") {
throw new AppError(403, "SQL_CONSOLE_DISABLED", "SQL console feature is disabled");
}
const guard = guardSql(sql, {
allowMultiStatement: user.isRoot,
readOnly: isReadOnly(user),
allowSchemaChanges: user.isRoot || user.permissions.some((grant) => grant.action === "schema_change")
});
const pool = getTargetPool();
const startedAt = Date.now();
const result = await pool.query(guard.normalized);
const durationMs = Date.now() - startedAt;
const maskedSql = guard.normalized.slice(0, 4000);
await createAuditEvent({
actorUserId: user.id,
action: "sql.execute",
resourceType: "sql_console",
resourceName: guard.statementType,
sqlTextMasked: maskedSql,
payloadAfter: {
rowCount: result.rowCount,
durationMs,
statementHash: createHash("sha256").update(guard.normalized).digest("hex")
},
ip: context.ip ?? null,
userAgent: context.userAgent ?? null,
status: "success"
});
return {
rows: result.rows,
fields: result.fields.map((field) => ({ name: field.name, dataTypeId: field.dataTypeID })),
rowCount: result.rowCount ?? 0,
durationMs,
statementType: guard.statementType,
notice: guard.isMutating ? "Mutation executed" : "Query executed"
};
}