UNPKG

bigquery-client

Version:

A feature-rich Node.js client for Google BigQuery with support for CRUD operations, transactions, query building, and advanced features like aggregate functions, pagination, and logging.

324 lines (323 loc) 12.3 kB
"use strict"; /** * @fileoverview Query Validation Utility - Advanced security and data validation for BigQuery operations * @version 1.0.6 * @author Pravin Jadhav * @description This module provides comprehensive validation capabilities including SQL injection protection, * parameter validation, schema validation, and security best practices for BigQuery operations. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryValidator = void 0; const errors_1 = require("../../errors"); /** * Advanced query validation and security system for BigQuery operations * * This class provides comprehensive validation capabilities including: * - SQL injection detection and prevention * - Parameter type and value validation * - Schema structure validation * - Query syntax validation * - Security best practices enforcement * - Input sanitization and normalization * * @class QueryValidator * @example * ```typescript * // Validate a query for security * try { * QueryValidator.validateQuery('SELECT * FROM users WHERE id = ?'); * console.log('Query is safe to execute'); * } catch (error) { * console.error('Query validation failed:', error.message); * } * * // Validate query parameters * QueryValidator.validateParameters([123, 'john@example.com', true]); * ``` */ class QueryValidator { /** * Validates SQL queries for security vulnerabilities and syntax issues * * This method provides comprehensive query validation including: * - SQL injection pattern detection * - Dangerous statement identification * - Comment and union-based attack prevention * - Query structure validation * - Security best practices enforcement * * @static * @param {string} query - SQL query string to validate * @returns {void} * @throws {ValidationError} When query contains security vulnerabilities or invalid syntax * * @example * ```typescript * // Valid queries pass validation * QueryValidator.validateQuery('SELECT * FROM users WHERE active = ?'); * QueryValidator.validateQuery('INSERT INTO logs (message, timestamp) VALUES (?, ?)'); * QueryValidator.validateQuery('UPDATE users SET last_login = ? WHERE id = ?'); * * // Invalid queries throw ValidationError * try { * QueryValidator.validateQuery("SELECT * FROM users; DROP TABLE users;"); * } catch (error) { * console.error('SQL injection detected:', error.message); * } * * try { * QueryValidator.validateQuery("SELECT * FROM users WHERE id = 1 OR '1'='1'"); * } catch (error) { * console.error('Injection pattern detected:', error.message); * } * * // Empty or invalid queries * try { * QueryValidator.validateQuery(''); * } catch (error) { * console.error('Invalid query format:', error.message); * } * ``` */ static validateQuery(query) { // Basic query format validation if (!query || typeof query !== 'string') { throw new errors_1.ValidationError('Invalid query: Query must be a non-empty string'); } // Advanced SQL injection detection patterns const sqlInjectionPatterns = [ /;\s*(DROP|ALTER|TRUNCATE|DELETE)\s+/i, // Dangerous statements after semicolon /UNION\s+SELECT.*FROM/i, // UNION-based injection attacks /\bEXEC\s*\(/i, // EXEC function calls /\bWAITFOR\s+DELAY/i, // Time-based injection attacks /--\s*$/, // SQL comments at end of query /\/\*.*\*\//, // Block comments for obfuscation /'\s*OR\s*'.*'=/i, // OR-based injection patterns /'\s*;\s*DROP/i // Drop statements after quotes ]; // Check each pattern for potential security threats for (const pattern of sqlInjectionPatterns) { if (pattern.test(query)) { throw new errors_1.ValidationError('Potential SQL injection detected'); } } } /** * Validates query parameters for type safety and security * * This method ensures parameter integrity by: * - Verifying parameter array structure * - Checking for null/undefined values * - Validating parameter types * - Preventing parameter-based injection * - Ensuring data consistency * * @static * @param {any[]} params - Array of query parameters to validate * @returns {void} * @throws {ValidationError} When parameters are invalid or potentially unsafe * * @example * ```typescript * // Valid parameter arrays * QueryValidator.validateParameters([123, 'john@example.com', true]); * QueryValidator.validateParameters(['active', new Date(), 42.5]); * QueryValidator.validateParameters([{ id: 1, name: 'test' }]); * * // Invalid parameters throw ValidationError * try { * QueryValidator.validateParameters('not an array'); * } catch (error) { * console.error('Parameters must be array:', error.message); * } * * try { * QueryValidator.validateParameters([123, null, 'valid']); * } catch (error) { * console.error('Null parameters not allowed:', error.message); * } * * try { * QueryValidator.validateParameters([undefined, 'test']); * } catch (error) { * console.error('Undefined parameters not allowed:', error.message); * } * * // Empty arrays are valid * QueryValidator.validateParameters([]); * ``` */ static validateParameters(params) { // Ensure parameters is an array if (!Array.isArray(params)) { throw new errors_1.ValidationError('Parameters must be an array'); } // Validate each parameter for safety for (const param of params) { if (param === undefined || param === null) { throw new errors_1.ValidationError('Parameters cannot be undefined or null'); } } } /** * Validates BigQuery table schema definitions for correctness and consistency * * This method provides schema validation including: * - Schema structure verification * - Field type validation * - Required field checking * - Data type compatibility * - Schema consistency enforcement * * @static * @param {any} schema - Schema object to validate * @returns {void} * @throws {ValidationError} When schema is invalid or malformed * * @example * ```typescript * // Valid schema objects (BigQuery types) * QueryValidator.validateSchema({ * id: 'INTEGER', * name: 'STRING', * email: 'STRING', * created_at: 'TIMESTAMP' * }); * * // Valid schema objects (JavaScript types) * QueryValidator.validateSchema({ * id: 'number', * name: 'string', * active: 'boolean', * created_at: 'timestamp' * }); * * QueryValidator.validateSchema({ * user_id: 'INTEGER', * metadata: 'JSON', * score: 'FLOAT', * active: 'BOOLEAN' * }); * * // Invalid schemas throw ValidationError * try { * QueryValidator.validateSchema(null); * } catch (error) { * console.error('Schema cannot be null:', error.message); * } * * try { * QueryValidator.validateSchema(['not', 'an', 'object']); * } catch (error) { * console.error('Schema must be object:', error.message); * } * * try { * QueryValidator.validateSchema('invalid schema'); * } catch (error) { * console.error('Schema format invalid:', error.message); * } * ``` */ static validateSchema(schema) { // Basic schema structure validation if (!schema || typeof schema !== 'object' || Array.isArray(schema)) { throw new errors_1.ValidationError('Invalid schema: Schema must be an object'); } // Validate schema fields and types const validBigQueryTypes = [ 'STRING', 'INTEGER', 'FLOAT', 'BOOLEAN', 'TIMESTAMP', 'DATE', 'TIME', 'DATETIME', 'GEOGRAPHY', 'NUMERIC', 'BIGNUMERIC', 'JSON', 'ARRAY', 'STRUCT' ]; // Also accept JavaScript types for convenience const validJavaScriptTypes = [ 'string', 'number', 'boolean', 'timestamp', 'date', 'time', 'datetime', 'geography', 'numeric', 'bignumeric', 'json', 'array', 'object' ]; const allValidTypes = [...validBigQueryTypes, ...validJavaScriptTypes]; for (const [fieldName, fieldType] of Object.entries(schema)) { // Validate field name if (!fieldName || typeof fieldName !== 'string') { throw new errors_1.ValidationError(`Invalid field name: ${fieldName}`); } // Validate field type if (typeof fieldType === 'string') { const lowerType = fieldType.toLowerCase(); const upperType = fieldType.toUpperCase(); if (!allValidTypes.includes(upperType) && !allValidTypes.includes(lowerType)) { throw new errors_1.ValidationError(`Invalid field type: ${fieldType} for field ${fieldName}`); } } } } /** * Validates table and column names for BigQuery compatibility * * @static * @param {string} name - Table or column name to validate * @returns {void} * @throws {ValidationError} When name doesn't meet BigQuery naming requirements * * @example * ```typescript * // Valid names * QueryValidator.validateIdentifier('users'); * QueryValidator.validateIdentifier('user_events'); * QueryValidator.validateIdentifier('analytics_2023'); * * // Invalid names * try { * QueryValidator.validateIdentifier('123_invalid'); * } catch (error) { * console.error('Names cannot start with numbers'); * } * ``` */ static validateIdentifier(name) { if (!name || typeof name !== 'string') { throw new errors_1.ValidationError('Identifier must be a non-empty string'); } // BigQuery identifier rules const identifierPattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/; if (!identifierPattern.test(name)) { throw new errors_1.ValidationError(`Invalid identifier: ${name}. Must start with letter or underscore, contain only letters, numbers, and underscores`); } // Check length limits if (name.length > 128) { throw new errors_1.ValidationError(`Identifier too long: ${name}. Maximum length is 128 characters`); } // Check reserved words const reservedWords = [ 'SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'DROP', 'ALTER', 'TABLE', 'INDEX', 'VIEW', 'DATABASE', 'SCHEMA', 'GRANT', 'REVOKE' ]; if (reservedWords.includes(name.toUpperCase())) { throw new errors_1.ValidationError(`Reserved word cannot be used as identifier: ${name}`); } } /** * Validates query complexity to prevent resource exhaustion * * @static * @param {string} query - SQL query to analyze * @returns {void} * @throws {ValidationError} When query is too complex or resource-intensive */ static validateQueryComplexity(query) { // Check for excessive JOINs const joinCount = (query.match(/\bJOIN\b/gi) || []).length; if (joinCount > 10) { throw new errors_1.ValidationError(`Query too complex: ${joinCount} JOINs detected. Maximum allowed: 10`); } // Check for excessive subqueries const subqueryCount = (query.match(/\(\s*SELECT\b/gi) || []).length; if (subqueryCount > 5) { throw new errors_1.ValidationError(`Query too complex: ${subqueryCount} subqueries detected. Maximum allowed: 5`); } // Check query length if (query.length > 10000) { throw new errors_1.ValidationError(`Query too long: ${query.length} characters. Maximum allowed: 10000`); } } } exports.QueryValidator = QueryValidator;