UNPKG

qgenutils

Version:

A security-first Node.js utility library providing authentication, HTTP operations, URL processing, validation, datetime formatting, and template rendering. Designed as a lightweight alternative to heavy npm packages with comprehensive error handling and

587 lines (418 loc) 14.7 kB
# QGenUtils Usage Guide ## Installation ```bash npm install qgenutils ``` ## Quick Start ```javascript const QGenUtils = require('qgenutils'); // import all utilities // Or import individual functions const { checkPassportAuth, requireFields, formatDateTime } = require('qgenutils'); ``` ## Authentication Utilities ### `checkPassportAuth(req)` Check if a user is authenticated via Passport.js with fail-closed security. ```javascript const { checkPassportAuth } = require('qgenutils'); app.get('/protected', (req, res) => { if (!checkPassportAuth(req)) { return res.status(401).json({ error: 'Authentication required' }); } res.json({ message: 'Welcome to protected area' }); }); ``` **Security Features:** - Returns `false` if Passport isn't configured - Returns `false` on any authentication errors - Logs all authentication attempts for security auditing ### `hasGithubStrategy()` Detect if GitHub OAuth strategy is configured in Passport.js. ```javascript const { hasGithubStrategy } = require('qgenutils'); app.get('/login-options', (req, res) => { const loginMethods = { local: true, // hasGithubStrategy reads global.passport to detect GitHub OAuth setup github: hasGithubStrategy() }; res.json(loginMethods); }); ``` ## Date and Time Utilities ### `formatDateTime(dateString)` Format ISO date strings to human-readable format with fallback handling. ```javascript const { formatDateTime } = require('qgenutils'); const userCreated = formatDateTime('2024-01-15T10:30:00.000Z'); // Returns: "1/15/2024, 10:30:00 AM" const invalidDate = formatDateTime('invalid-date'); // Returns: "N/A" const nullDate = formatDateTime(null); // Returns: "N/A" ``` **Features:** - Automatic fallback to "N/A" for invalid dates - Handles null/undefined inputs gracefully - Uses locale-appropriate formatting ### `formatDuration(startDate, endDate?)` Calculate and format duration between dates in HH:MM:SS format. ```javascript const { formatDuration } = require('qgenutils'); const start = '2024-01-15T10:00:00.000Z'; const end = '2024-01-15T12:30:45.000Z'; const duration = formatDuration(start, end); // Returns: "02:30:45" // Using current time as end date const ongoing = formatDuration(start); // Returns duration from start to now ``` ## HTTP Utilities ### `calculateContentLength(body)` Calculate accurate byte length for HTTP bodies with UTF-8 support. ```javascript const { calculateContentLength } = require('qgenutils'); const textBody = "Hello, 世界!"; const length = calculateContentLength(textBody); // Returns: "13" (accounts for UTF-8 encoding) const jsonBody = { message: "Hello" }; const jsonLength = calculateContentLength(jsonBody); // Returns: "20" (JSON.stringify byte length) const buf = Buffer.from('Hi'); const bufLength = calculateContentLength(buf); // Returns: "2" (binary length) // Buffer example ``` ### `buildCleanHeaders(headers, method, body)` Remove dangerous headers (for example `host` and `x-target-url`) and recalculate content-length for proxy security. ```javascript const { buildCleanHeaders } = require('qgenutils'); const originalHeaders = { 'host': 'example.com', 'x-target-url': 'https://backend.internal', // will be removed 'content-length': '100', 'authorization': 'Bearer token123' }; const cleanHeaders = buildCleanHeaders(originalHeaders, 'POST', { data: 'test' }); // Returns headers without 'host' and 'x-target-url', with recalculated content-length ``` You can inspect the list of stripped headers via the exported constant: ```javascript const { HEADERS_TO_REMOVE } = require('qgenutils/lib/http'); console.log(HEADERS_TO_REMOVE); ``` **Security Features:** - Removes proxy-related headers that could cause issues - Recalculates content-length to prevent HTTP smuggling - Handles GET requests specially (removes content-length) ### `HEADERS_TO_REMOVE` constant Array of headers automatically stripped when building clean headers. ```javascript const { HEADERS_TO_REMOVE } = require('qgenutils'); console.log(HEADERS_TO_REMOVE); // [ 'host', 'x-target-url', ... ] ``` ### `getRequiredHeader(req, res, headerName, statusCode, errorMessage)` Extract required headers with automatic error responses. ```javascript const { getRequiredHeader } = require('qgenutils'); app.post('/api/upload', (req, res) => { const contentType = getRequiredHeader(req, res, 'content-type', 400, 'Content-Type required'); if (!contentType) return; // Error response already sent // Process upload... }); ``` ## URL Utilities ### `ensureProtocol(url)` Add HTTPS protocol to URLs that don't have one (security-first default). ```javascript const { ensureProtocol } = require('qgenutils'); const httpsUrl = ensureProtocol('example.com'); // Returns: "https://example.com" const existingProtocol = ensureProtocol('http://example.com'); // Returns: "http://example.com" (preserves existing) const invalid = ensureProtocol(''); // Returns: null ``` ### `normalizeUrlOrigin(url)` Normalize URLs to lowercase origins for comparison and caching. ```javascript const { normalizeUrlOrigin } = require('qgenutils'); const normalized = normalizeUrlOrigin('HTTP://EXAMPLE.COM/path?query=1'); // Returns: "http://example.com" const withPort = normalizeUrlOrigin('https://api.example.com:8080/endpoint'); // Returns: "https://api.example.com:8080" ``` ### `stripProtocol(url)` Remove protocol and trailing slash for display purposes. ```javascript const { stripProtocol } = require('qgenutils'); const clean = stripProtocol('https://example.com/'); // Returns: "example.com" const withPath = stripProtocol('http://api.example.com/v1/users'); // Returns: "api.example.com/v1/users" ``` ### `parseUrlParts(url)` Split URLs into base URL and endpoint components. ```javascript const { parseUrlParts } = require('qgenutils'); const parts = parseUrlParts('https://api.example.com/v1/users?limit=10'); // Returns: { // baseUrl: "https://api.example.com", // endpoint: "/v1/users?limit=10" // } ``` ## Validation Utilities ### `requireFields(obj, requiredFields, res)` Validate required fields with detailed error responses. ```javascript const { requireFields } = require('qgenutils'); app.post('/api/users', (req, res) => { if (!requireFields(req.body, ['name', 'email', 'password'], res)) { return; // Error response already sent } // All required fields are present // Continue with user creation... }); ``` **Features:** - Lists ALL missing fields in single response - Treats falsy values (null, '', 0, false) as missing - Sends standardized error responses automatically - Returns boolean for simple conditional logic **Example Error Response:** ```json { "error": "Missing required fields", "missing": ["email", "password"] } ``` ## Response Utilities ### `sendJsonResponse(res, statusCode, data)` Send standardized JSON responses with proper headers. ```javascript const { sendJsonResponse } = require('qgenutils'); app.get('/api/users', (req, res) => { const users = getUsersFromDatabase(); sendJsonResponse(res, 200, { users, count: users.length }); }); ``` ### `sendValidationError(res, message, additionalData?, statusCode?)` Send standardized validation error responses. ```javascript const { sendValidationError } = require('qgenutils'); if (age < 18) { return sendValidationError(res, 'Must be 18 or older', { provided: age, minimum: 18 }, 400); } ``` ### `sendAuthError(res, message?)` Send standardized authentication error responses. ```javascript const { sendAuthError } = require('qgenutils'); if (!isValidToken(token)) { return sendAuthError(res, 'Invalid or expired token'); } ``` ### `sendServerError(res, message?, error?, context?)` Send standardized server error responses with internal logging. ```javascript const { sendServerError } = require('qgenutils'); try { const result = await complexDatabaseOperation(); sendJsonResponse(res, 200, result); } catch (error) { sendServerError(res, 'Database operation failed', error, 'getUserProfile'); } ``` ## View Utilities ### `renderView(res, viewName, errorTitle)` Render EJS templates with graceful error handling. ```javascript const { renderView } = require('qgenutils'); app.get('/dashboard', (req, res) => { renderView(res, 'dashboard', 'Dashboard Error'); }); ``` **Error Handling:** - Shows user-friendly error page if template fails - Logs detailed error information for debugging - Provides navigation back to home page - Prevents application crashes from template errors ### `registerViewRoute(routePath, viewName, errorTitle)` Register simple view routes with built-in error handling. The function uses the global `app` object, so you only provide the route path and view name. ```javascript const { registerViewRoute } = require('qgenutils'); registerViewRoute('/about', 'about', 'About Page Error'); registerViewRoute('/contact', 'contact', 'Contact Page Error'); ``` ## Input Validation Utilities ### `isValidObject(obj)` Check if value is a plain object (not array or null). ```javascript const { isValidObject } = require('qgenutils/lib/input-validation'); isValidObject({ name: 'John' }); // true isValidObject([1, 2, 3]); // false isValidObject(null); // false isValidObject('string'); // false ``` ### `isValidString(str)` Check if value is a non-empty string after trimming. ```javascript const { isValidString } = require('qgenutils/lib/input-validation'); isValidString('hello'); // true isValidString(' '); // false (only whitespace) isValidString(''); // false isValidString(null); // false ``` ### `hasMethod(obj, methodName)` Check if object has a specific method. ```javascript const { hasMethod } = require('qgenutils/lib/input-validation'); hasMethod(res, 'json'); // true for Express response hasMethod({}, 'toString'); // true (inherited method) hasMethod(null, 'method'); // false ``` ### `isValidExpressResponse(res)` Check if object is a valid Express response object. ```javascript const { isValidExpressResponse } = require('qgenutils/lib/input-validation'); app.use((req, res, next) => { if (!isValidExpressResponse(res)) { throw new Error('Invalid response object'); } next(); }); ``` ## Error Handling Patterns ### Basic Error Handling ```javascript const { checkPassportAuth, sendAuthError, sendServerError } = require('qgenutils'); app.get('/protected', (req, res) => { try { if (!checkPassportAuth(req)) { return sendAuthError(res); } // Protected logic here sendJsonResponse(res, 200, { data: 'success' }); } catch (error) { sendServerError(res, 'Operation failed', error, 'protectedRoute'); } }); ``` ### Validation Chain Pattern ```javascript const { requireFields, sendValidationError } = require('qgenutils'); const { isValidString } = require('qgenutils/lib/input-validation'); app.post('/api/users', (req, res) => { // Step 1: Check required fields if (!requireFields(req.body, ['name', 'email'], res)) { return; // Error response already sent } // Step 2: Additional validation if (!isValidString(req.body.name) || req.body.name.length < 2) { return sendValidationError(res, 'Name must be at least 2 characters'); } // Step 3: Process valid request createUser(req.body); sendJsonResponse(res, 201, { success: true }); }); ``` ## Best Practices ### 1. Always Handle Authentication First ```javascript app.use('/api/protected/*', (req, res, next) => { if (!checkPassportAuth(req)) { return sendAuthError(res); } next(); }); ``` ### 2. Use Validation Chains for Complex Input ```javascript function validateUserInput(req, res) { if (!requireFields(req.body, ['email', 'password'], res)) return false; if (!isValidString(req.body.email)) { sendValidationError(res, 'Valid email required'); return false; } return true; } ``` ### 3. Standardize URL Processing ```javascript function processUserUrl(userInput) { const urlWithProtocol = ensureProtocol(userInput); // Adds HTTPS const normalized = normalizeUrlOrigin(urlWithProtocol); // For comparison const { baseUrl, endpoint } = parseUrlParts(urlWithProtocol); // For routing return { normalized, baseUrl, endpoint }; } ``` ### 4. Consistent Error Responses ```javascript // Good: Use utility functions for consistent responses sendValidationError(res, 'Invalid input', { field: 'email' }); sendAuthError(res, 'Token expired'); sendServerError(res, 'Database error', error, 'userCreate'); // Avoid: Manual response construction res.status(400).json({ error: 'Bad request' }); // Inconsistent format ``` ### 5. Leverage Built-in Logging All utilities automatically log operations for debugging: ```javascript // Automatic logging is built into all functions const duration = formatDuration(start, end); // Logs: "formatDuration is running with 2024-01-15T10:00:00.000Z and 2024-01-15T12:00:00.000Z" // Logs: "formatDuration is returning 02:00:00" ``` You can also log your own messages using the provided logger instance. Logs rotate daily via `winston-daily-rotate-file`. ```javascript const { logger } = require('qgenutils'); logger.info('Server started'); ``` ## Common Use Cases ### API Endpoint Template ```javascript const { checkPassportAuth, requireFields, sendJsonResponse, sendAuthError, sendServerError } = require('qgenutils'); app.post('/api/resource', async (req, res) => { try { // Authentication if (!checkPassportAuth(req)) { return sendAuthError(res); } // Validation if (!requireFields(req.body, ['name', 'type'], res)) { return; } // Business logic const result = await createResource(req.body); // Success response sendJsonResponse(res, 201, { resource: result }); } catch (error) { sendServerError(res, 'Failed to create resource', error, 'createResource'); } }); ``` ### Proxy Request Processing ```javascript const { buildCleanHeaders, ensureProtocol, parseUrlParts } = require('qgenutils'); async function proxyRequest(req, res) { const targetUrl = ensureProtocol(req.body.url); const { baseUrl, endpoint } = parseUrlParts(targetUrl); const cleanHeaders = buildCleanHeaders(req.headers, req.method, req.body); // Forward request with cleaned headers const response = await fetch(`${baseUrl}${endpoint}`, { method: req.method, headers: cleanHeaders, body: req.body ? JSON.stringify(req.body) : undefined }); sendJsonResponse(res, response.status, await response.json()); } ``` This usage guide covers all major functionality with practical examples and security considerations.