@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
270 lines (253 loc) • 8.46 kB
JavaScript
/**
* 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