88 lines
2.3 KiB
JavaScript
88 lines
2.3 KiB
JavaScript
const express = require('express');
|
|
const db = require('../db/models');
|
|
const wrapAsync = require('../helpers').wrapAsync;
|
|
const { validateReadOnlySql } = require('../utils/sqlValidator');
|
|
|
|
const router = express.Router();
|
|
const MAX_SQL_LENGTH = 5000;
|
|
const MAX_SQL_ROWS = 1000;
|
|
const SQL_TIMEOUT_MS = 5000;
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/sql:
|
|
* post:
|
|
* security:
|
|
* - bearerAuth: []
|
|
* summary: Execute a SELECT-only SQL query
|
|
* description: Executes a read-only SQL query and returns rows.
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* sql:
|
|
* type: string
|
|
* required:
|
|
* - sql
|
|
* responses:
|
|
* 200:
|
|
* description: Query result
|
|
* 400:
|
|
* description: Invalid SQL
|
|
* 401:
|
|
* $ref: "#/components/responses/UnauthorizedError"
|
|
* 500:
|
|
* description: Internal server error
|
|
*/
|
|
router.post(
|
|
'/',
|
|
wrapAsync(async (req, res) => {
|
|
const { currentUser } = req;
|
|
const isAdminUser = Boolean(
|
|
currentUser &&
|
|
currentUser.app_role &&
|
|
(currentUser.app_role.name === 'Administrator' ||
|
|
currentUser.app_role.globalAccess === true),
|
|
);
|
|
|
|
if (!isAdminUser) {
|
|
return res
|
|
.status(403)
|
|
.json({ error: 'Only administrators can execute SQL queries' });
|
|
}
|
|
|
|
const { sql } = req.body;
|
|
const validation = validateReadOnlySql(sql, { maxLength: MAX_SQL_LENGTH });
|
|
if (!validation.valid) {
|
|
return res.status(400).json({ error: validation.error });
|
|
}
|
|
|
|
const normalized = validation.normalized;
|
|
const wrappedSql = `SELECT * FROM (${normalized}) AS query_result LIMIT ${MAX_SQL_ROWS}`;
|
|
|
|
const rows = await db.sequelize.transaction(async (transaction) => {
|
|
await db.sequelize.query(
|
|
`SET LOCAL statement_timeout = ${SQL_TIMEOUT_MS}`,
|
|
{ transaction },
|
|
);
|
|
return db.sequelize.query(wrappedSql, {
|
|
transaction,
|
|
type: db.Sequelize.QueryTypes.SELECT,
|
|
});
|
|
});
|
|
|
|
return res.status(200).json({
|
|
rows,
|
|
meta: {
|
|
maxRows: MAX_SQL_ROWS,
|
|
statementTimeoutMs: SQL_TIMEOUT_MS,
|
|
},
|
|
});
|
|
}),
|
|
);
|
|
|
|
module.exports = router;
|