UNPKG

cookie-to-guard

Version:

Library for protecting cookies from tampering

246 lines (215 loc) 9.15 kB
/** * CookieGuard - Advanced cookie protection library * @version 1.0.0 * @license MIT */ // Генерация HMAC подписи (более безопасный вариант) const generateHMAC = (message, secret) => { // В реальной реализации используйте криптографические библиотеки // Это упрощенный пример для демонстрации let hash = 0; for (let i = 0; i < message.length; i++) { const char = message.charCodeAt(i); hash = (hash << 5) - hash + char; hash |= 0; // Преобразование в 32-битное целое } return hash.toString(36) + secret; }; class CookieGuard { /** * @param {Object} options - Configuration options * @param {string} options.cookieName - Name of cookie to protect * @param {string} [options.redirectUrl] - Redirect URL when tampering detected * @param {Function} [options.onDetect] - Callback when tampering detected * @param {boolean} [options.autoReset=true] - Auto-reset cookie on tampering * @param {string} [options.domain] - Cookie domain * @param {boolean} [options.secure=false] - Secure cookie flag * @param {string} [options.sameSite='Lax'] - SameSite cookie attribute */ constructor(options) { if (!options || !options.cookieName) { throw new Error('CookieGuard: cookieName is required'); } this.config = { cookieName: options.cookieName, signatureName: `${options.cookieName}_sig`, redirectUrl: options.redirectUrl || null, onDetect: options.onDetect || null, autoReset: options.autoReset !== false, domain: options.domain || null, secure: options.secure || false, sameSite: options.sameSite || 'Lax', secret: options.secret || this._generateSecret() }; this._initialize(); } // ... (остальные методы остаются аналогичными, но с улучшениями) /** * Устанавливает защищенную куку * @param {string} value - Значение куки * @param {Object} [options] - Дополнительные параметры * @param {number} [options.days=365] - Срок жизни в днях * @param {string} [options.path='/'] - Путь куки */ setSecureCookie(value, options = {}) { const { days = 365, path = '/' } = options; const signature = generateHMAC(value, this.config.secret); let cookieString = `${this.config.cookieName}=${encodeURIComponent(value)}; ` + `expires=${new Date(Date.now() + days * 864e5).toUTCString()}; ` + `path=${path}; ` + `SameSite=${this.config.sameSite}; `; if (this.config.domain) cookieString += `domain=${this.config.domain}; `; if (this.config.secure) cookieString += 'Secure; '; document.cookie = cookieString; // Устанавливаем подпись document.cookie = `${this.config.signatureName}=${encodeURIComponent(signature)}; ` + cookieString.split('; ').slice(1).join('; '); } _initialize() { if (this.initialized) return; // Проверяем куки при загрузке this._verifyCookie(); // Устанавливаем обработчик для проверки куки перед отправкой на сервер if (typeof window !== 'undefined') { const originalFetch = window.fetch; const that = this; window.fetch = async function(...args) { that._verifyCookie(); return originalFetch.apply(this, args); }; // Также можно перехватывать XMLHttpRequest, но для современного кода fetch достаточно } this.initialized = true; } /** * Генерация секретного ключа для подписи * @returns {string} * @private */ _generateSecretKey() { // В реальном приложении лучше использовать более сложную логику генерации return 'cg_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } /** * Получает значение куки по имени * @param {string} name - Имя куки * @returns {string|null} * @private */ _getCookie(name) { const cookies = document.cookie.split(';'); for (let cookie of cookies) { cookie = cookie.trim(); if (cookie.startsWith(name + '=')) { return decodeURIComponent(cookie.substring(name.length + 1)); } } return null; } /** * Устанавливает куки * @param {string} name - Имя куки * @param {string} value - Значение куки * @param {number} days - Срок жизни в днях * @private */ _setCookie(name, value, days = 365) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); const expires = 'expires=' + date.toUTCString(); document.cookie = name + '=' + encodeURIComponent(value) + ';' + expires + ';path=/'; } /** * Удаляет куки * @param {string} name - Имя куки * @private */ _deleteCookie(name) { document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; } /** * Создает подпись для значения куки * @param {string} value - Значение куки * @returns {string} * @private */ _createSignature(value) { // В реальном приложении следует использовать более надежный алгоритм подписи return value.split('').reverse().join('') + this.secretKey; } /** * Проверяет подпись куки * @param {string} value - Значение куки * @param {string} signature - Подпись * @returns {boolean} * @private */ _verifySignature(value, signature) { return this._createSignature(value) === signature; } /** * Проверяет целостность куки * @private */ _verifyCookie() { const cookieValue = this._getCookie(this.cookieName); const signatureValue = this._getCookie(this.cookieName + '_sig'); if (cookieValue && signatureValue) { if (!this._verifySignature(cookieValue, signatureValue)) { this._handleCookieTampering(); } } else if (cookieValue && !signatureValue) { // Куки есть, но подписи нет - возможно, первое использование this._setCookie(this.cookieName + '_sig', this._createSignature(cookieValue)); } } /** * Обработка обнаружения изменения куки * @private */ _handleCookieTampering() { if (this.onDetect && typeof this.onDetect === 'function') { this.onDetect(); } if (this.autoReset) { this._deleteCookie(this.cookieName); this._deleteCookie(this.cookieName + '_sig'); } if (this.redirectUrl) { window.location.href = this.redirectUrl; } } /** * Устанавливает защищенное значение куки * @param {string} value - Значение куки * @param {number} [days=365] - Срок жизни в днях */ setSecureCookie(value, days = 365) { this._setCookie(this.cookieName, value, days); this._setCookie(this.cookieName + '_sig', this._createSignature(value), days); } /** * Получает защищенное значение куки * @returns {string|null} */ getSecureCookie() { this._verifyCookie(); return this._getCookie(this.cookieName); } /** * Удаляет защищенную куки */ deleteSecureCookie() { this._deleteCookie(this.cookieName); this._deleteCookie(this.cookieName + '_sig'); } } // Экспорт для разных сред if (typeof module !== 'undefined' && module.exports) { module.exports = CookieGuard; } else if (typeof define === 'function' && define.amd) { define([], () => CookieGuard); } else if (typeof window !== 'undefined') { window.CookieGuard = CookieGuard; }