UNPKG

@ordojs/security

Version:

Security package for OrdoJS with XSS, CSRF, and injection protection

270 lines (253 loc) 8.46 kB
/** * CSRF Manager * Main interface for CSRF protection functionality */ import { CSRFSessionManager } from './session-manager'; import { CSRFTokenGenerator } from './token-generator'; export class CSRFManager { tokenGenerator; sessionManager; config; constructor(config) { this.tokenGenerator = new CSRFTokenGenerator(config); this.config = this.tokenGenerator.getConfig(); this.sessionManager = new CSRFSessionManager(this.config); } /** * Generate a new CSRF token for a session */ generateToken(sessionId) { const token = this.tokenGenerator.generateToken(sessionId); this.sessionManager.addToken(sessionId, token); return token; } /** * Validate a CSRF token using session-based validation */ validateToken(tokenValue, sessionId) { // First validate the token structure and signature const tokenValidation = this.tokenGenerator.validateToken(tokenValue, sessionId); if (!tokenValidation.valid) { return tokenValidation; } // Then check if the token exists in the session const sessionValidation = this.sessionManager.validateSessionToken(sessionId, tokenValue); return sessionValidation; } /** * Generate and set up double-submit cookie protection */ setupDoubleSubmitProtection(sessionId) { const cookieToken = this.tokenGenerator.generateCookieToken(); return { headers: { [this.config.headerName]: cookieToken }, cookies: [{ name: this.config.cookieName, value: cookieToken, options: { httpOnly: this.config.httpOnlyCookie, secure: this.config.secureCookie, sameSite: this.config.sameSite, maxAge: Math.floor(this.config.tokenExpiry / 1000), path: '/' } }] }; } /** * Validate double-submit cookie pattern */ validateDoubleSubmit(request) { const cookieToken = request.cookies?.[this.config.cookieName]; const headerToken = request.headers[this.config.headerName] || request.headers[this.config.headerName.toLowerCase()]; const validation = this.tokenGenerator.validateDoubleSubmit(cookieToken || '', headerToken || ''); return validation; } /** * Validate CSRF protection for a request */ validateRequest(request) { // If session ID is provided, use session-based validation if (request.sessionId) { const tokenValue = this.extractTokenFromRequest(request); if (!tokenValue) { return { valid: false, error: 'CSRF token not found in request' }; } return this.validateToken(tokenValue, request.sessionId); } // Otherwise, use double-submit cookie pattern return this.validateDoubleSubmit(request); } /** * Extract CSRF token from request (header or form field) */ extractTokenFromRequest(request) { // Check header first const headerToken = request.headers[this.config.headerName] || request.headers[this.config.headerName.toLowerCase()]; if (headerToken) { return headerToken; } // Check form field if (request.body && typeof request.body === 'object') { return request.body[this.config.fieldName] || null; } return null; } /** * Generate HTML form field for CSRF token */ generateFormField(sessionId) { const token = this.generateToken(sessionId); return `<input type="hidden" name="${this.config.fieldName}" value="${token.value}" />`; } /** * Generate JavaScript code for automatic token injection */ generateClientScript(sessionId) { const config = { headerName: this.config.headerName, fieldName: this.config.fieldName, cookieName: this.config.cookieName }; return ` (function() { const csrfConfig = ${JSON.stringify(config)}; // Function to get cookie value function getCookie(name) { const value = "; " + document.cookie; const parts = value.split("; " + name + "="); if (parts.length === 2) return parts.pop().split(";").shift(); return null; } // Function to get CSRF token function getCSRFToken() { // Try to get from cookie first (double-submit pattern) const cookieToken = getCookie(csrfConfig.cookieName); if (cookieToken) return cookieToken; // Try to get from meta tag const metaTag = document.querySelector('meta[name="csrf-token"]'); if (metaTag) return metaTag.getAttribute('content'); return null; } // Inject CSRF token into forms function injectFormTokens() { const token = getCSRFToken(); if (!token) return; const forms = document.querySelectorAll('form'); forms.forEach(form => { // Skip if token already exists if (form.querySelector('input[name="' + csrfConfig.fieldName + '"]')) return; const input = document.createElement('input'); input.type = 'hidden'; input.name = csrfConfig.fieldName; input.value = token; form.appendChild(input); }); } // Inject CSRF token into AJAX requests function setupAjaxInterceptor() { const token = getCSRFToken(); if (!token) return; // XMLHttpRequest interceptor const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url, async, user, password) { this._method = method; return originalOpen.call(this, method, url, async, user, password); }; XMLHttpRequest.prototype.send = function(data) { if (this._method && this._method.toUpperCase() !== 'GET') { this.setRequestHeader(csrfConfig.headerName, token); } return originalSend.call(this, data); }; // Fetch interceptor if (window.fetch) { const originalFetch = window.fetch; window.fetch = function(input, init) { init = init || {}; const method = init.method || 'GET'; if (method.toUpperCase() !== 'GET') { init.headers = init.headers || {}; if (typeof init.headers.set === 'function') { init.headers.set(csrfConfig.headerName, token); } else { init.headers[csrfConfig.headerName] = token; } } return originalFetch.call(this, input, init); }; } } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { injectFormTokens(); setupAjaxInterceptor(); }); } else { injectFormTokens(); setupAjaxInterceptor(); } // Re-inject tokens when new forms are added const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { // Element node if (node.tagName === 'FORM') { injectFormTokens(); } else if (node.querySelectorAll) { const forms = node.querySelectorAll('form'); if (forms.length > 0) { injectFormTokens(); } } } }); } }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); `.trim(); } /** * Consume a token (for one-time use) */ consumeToken(sessionId, tokenValue) { return this.sessionManager.consumeToken(sessionId, tokenValue); } /** * Remove a session and all its tokens */ removeSession(sessionId) { return this.sessionManager.removeSession(sessionId); } /** * Get session statistics */ getStats() { return this.sessionManager.getStats(); } /** * Clean up resources */ destroy() { this.sessionManager.destroy(); } /** * Get configuration */ getConfig() { return { ...this.config }; } } //# sourceMappingURL=csrf-manager.js.map