59 lines
1.9 KiB
TypeScript
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"
|
|
};
|
|
}
|