Harden auth session handling and SQL identifier validation
This commit is contained in:
@@ -13,8 +13,9 @@ $container = new Container();
|
||||
AppFactory::setContainer($container);
|
||||
$app = AppFactory::create();
|
||||
|
||||
// В dev включаем подробные ошибки (можно выключить в проде)
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
// Подробные ошибки только если явно включены
|
||||
$displayErrors = filter_var(getenv('PHP_ERROR_DISPLAY') ?: 'false', FILTER_VALIDATE_BOOLEAN);
|
||||
$app->addErrorMiddleware($displayErrors, true, true);
|
||||
|
||||
$container->set('db', function () {
|
||||
return \App\Db::connectFromSession();
|
||||
@@ -38,6 +39,7 @@ $app->post('/api/login', function (Request $request, Response $response) {
|
||||
|
||||
try {
|
||||
\App\Db::testConnection($user, $pass);
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['db_user'] = $user;
|
||||
$_SESSION['db_pass'] = $pass;
|
||||
|
||||
@@ -53,10 +55,36 @@ $app->post('/api/login', function (Request $request, Response $response) {
|
||||
->withStatus($status);
|
||||
});
|
||||
|
||||
// Текущий статус сессии
|
||||
$app->get('/api/session', function (Request $request, Response $response) {
|
||||
$authenticated = !empty($_SESSION['db_user']) && !empty($_SESSION['db_pass']);
|
||||
$payload = [
|
||||
'authenticated' => $authenticated,
|
||||
'user' => $authenticated ? $_SESSION['db_user'] : null,
|
||||
];
|
||||
|
||||
$response->getBody()->write(json_encode($payload));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
});
|
||||
|
||||
// Выход из системы: гарантированно очищаем серверную сессию
|
||||
$app->post('/api/logout', function (Request $request, Response $response) {
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
|
||||
}
|
||||
session_destroy();
|
||||
|
||||
$response->getBody()->write(json_encode(['ok' => true]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
});
|
||||
|
||||
// Middleware на /api/*: проверяем, что аутентифицированы
|
||||
$app->add(function (Request $request, $handler) {
|
||||
$path = $request->getUri()->getPath();
|
||||
if (str_starts_with($path, '/api/') && $path !== '/api/login') {
|
||||
$publicApi = ['/api/login', '/api/session', '/api/logout'];
|
||||
if (str_starts_with($path, '/api/') && !in_array($path, $publicApi, true)) {
|
||||
if (empty($_SESSION['db_user']) || empty($_SESSION['db_pass'])) {
|
||||
$res = new \Slim\Psr7\Response();
|
||||
$res->getBody()->write(json_encode(['error' => 'Not authenticated']));
|
||||
@@ -101,23 +129,13 @@ $app->post('/api/table/data', function (Request $request, Response $response) us
|
||||
$pageSize = (int)($payload['size'] ?? $payload['pageSize'] ?? 50);
|
||||
$filters = $payload['filters'] ?? [];
|
||||
$sort = $payload['sort'] ?? null;
|
||||
$columns = $payload['columns'] ?? [];
|
||||
|
||||
// ✅ Логирование для отладки
|
||||
error_log("Data request: page=$page, pageSize=$pageSize, filters=" . json_encode($filters) . ", sort=" . json_encode($sort));
|
||||
|
||||
$pdo = $container->get('db');
|
||||
$meta = new \App\MetaService($pdo);
|
||||
$ds = new \App\DataService($pdo);
|
||||
$metaArr = $meta->getTableMeta($schema, $table);
|
||||
$columns = $metaArr['columns'];
|
||||
$resData = $ds->fetchData($schema, $table, $columns, $page, $pageSize, $filters, $sort);
|
||||
|
||||
// ✅ Логирование ответа
|
||||
error_log("Data response: " . json_encode([
|
||||
'data_count' => count($resData['data']),
|
||||
'last_page' => $resData['last_page'],
|
||||
'current_page' => $resData['current_page'],
|
||||
'total' => $resData['total']
|
||||
]));
|
||||
|
||||
$response->getBody()->write(json_encode($resData));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
});
|
||||
@@ -267,7 +285,14 @@ $app->post('/api/table/export-csv', function (Request $request, Response $respon
|
||||
$table = $payload['table'] ?? '';
|
||||
$filters = $payload['filters'] ?? [];
|
||||
$sort = $payload['sort'] ?? null;
|
||||
$columns = $payload['columns'] ?? [];
|
||||
$meta = new \App\MetaService($pdo);
|
||||
$metaArr = $meta->getTableMeta($schema, $table);
|
||||
$requestedColumns = $payload['columns'] ?? [];
|
||||
$requestedNames = array_values(array_filter(array_map(fn($c) => $c['COLUMN_NAME'] ?? '', $requestedColumns)));
|
||||
$columns = array_values(array_filter(
|
||||
$metaArr['columns'],
|
||||
fn($c) => in_array($c['COLUMN_NAME'], $requestedNames, true)
|
||||
));
|
||||
|
||||
$result = $ds->exportCSV($schema, $table, $columns, $filters, $sort);
|
||||
|
||||
@@ -284,6 +309,11 @@ $app->get('/api/fk-values', function (Request $request, Response $response) use
|
||||
$search = $params['search'] ?? ''; // ✅ Добавлен параметр поиска
|
||||
|
||||
$pdo = $container->get('db');
|
||||
$isSafeIdentifier = fn(string $v): bool => preg_match('/^[A-Za-z0-9_]+$/', $v) === 1;
|
||||
if (!$isSafeIdentifier($schema) || !$isSafeIdentifier($table) || !$isSafeIdentifier($column)) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Invalid schema/table/column name']));
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
// ✅ Сначала проверяем количество записей
|
||||
$countSql = "SELECT COUNT(DISTINCT `{$column}`) as cnt
|
||||
|
||||
Reference in New Issue
Block a user