1111
This commit is contained in:
58
backend/src/modules/sql-console/sql-console.service.ts
Normal file
58
backend/src/modules/sql-console/sql-console.service.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user