Lin / frontend /src /services /cookieService.js
Zelyanoth's picture
fff
25f22bf
raw
history blame
6.53 kB
import cacheService from './cacheService';
class CookieService {
/**
* Set a cookie with enhanced security for HTTP-only storage
* @param {string} name - Cookie name
* @param {string} value - Cookie value
* @param {Object} options - Cookie options
* @returns {Promise<void>}
*/
async set(name, value, options = {}) {
// Store in secure cache as fallback
const cacheOptions = {
httpOnly: true,
secure: true,
sameSite: 'Strict',
...options
};
// Calculate TTL for cache
let ttl = 60 * 60 * 1000; // Default 1 hour
if (options.expires) {
ttl = options.expires.getTime() - Date.now();
} else if (options.maxAge) {
ttl = options.maxAge * 1000;
}
await cacheService.set(`cookie_${name}`, { value, options }, ttl);
// For client-side simulation, we'll use localStorage with security prefixes
// In a real implementation, HTTP-only cookies would be set by the server
const cookieData = {
value,
options: {
secure: cacheOptions.secure,
sameSite: cacheOptions.sameSite,
expires: options.expires?.toISOString() || null,
maxAge: options.maxAge || null
}
};
try {
localStorage.setItem(`__secure_cookie_${name}`, JSON.stringify(cookieData));
if (import.meta.env.VITE_NODE_ENV === 'development') {
console.log(`πŸͺ [Cookie] Set secure cookie: ${name}`);
}
} catch (error) {
console.warn(`πŸͺ [Cookie] Could not set localStorage cookie: ${name}`, error);
}
}
/**
* Get a cookie value
* @param {string} name - Cookie name
* @returns {Promise<string|null>} - Cookie value or null
*/
async get(name) {
// Try to get from secure cache first
const cached = await cacheService.get(`cookie_${name}`);
if (cached) {
return cached.value;
}
// Fallback to localStorage simulation
try {
const cookieData = localStorage.getItem(`__secure_cookie_${name}`);
if (cookieData) {
const parsed = JSON.parse(cookieData);
// Check expiration
if (parsed.options.expires) {
const expiry = new Date(parsed.options.expires);
if (Date.now() > expiry.getTime()) {
await this.remove(name);
return null;
}
}
return parsed.value;
}
} catch (error) {
console.warn(`πŸͺ [Cookie] Could not read localStorage cookie: ${name}`, error);
}
return null;
}
/**
* Remove a cookie
* @param {string} name - Cookie name
* @returns {Promise<void>}
*/
async remove(name) {
// Remove from cache
await cacheService.remove(`cookie_${name}`);
// Remove from localStorage simulation
try {
localStorage.removeItem(`__secure_cookie_${name}`);
if (import.meta.env.VITE_NODE_ENV === 'development') {
console.log(`πŸͺ [Cookie] Removed cookie: ${name}`);
}
} catch (error) {
console.warn(`πŸͺ [Cookie] Could not remove localStorage cookie: ${name}`, error);
}
}
/**
* Clear all cookies
* @returns {Promise<void>}
*/
async clear() {
// Clear cache entries
const keys = (await cacheService.storage.keys()) || [];
const cookieKeys = keys.filter(key => key.startsWith('cookie_'));
await Promise.all(cookieKeys.map(key => cacheService.remove(key)));
// Clear localStorage simulation
try {
const localStorageKeys = Object.keys(localStorage);
const cookieLocalStorageKeys = localStorageKeys.filter(key => key.startsWith('__secure_cookie_'));
cookieLocalStorageKeys.forEach(key => localStorage.removeItem(key));
if (import.meta.env.VITE_NODE_ENV === 'development') {
console.log('πŸͺ [Cookie] Cleared all cookies');
}
} catch (error) {
console.warn('πŸͺ [Cookie] Could not clear all localStorage cookies', error);
}
}
/**
* Check if a cookie exists
* @param {string} name - Cookie name
* @returns {Promise<boolean>} - True if cookie exists
*/
async exists(name) {
return (await this.get(name)) !== null;
}
/**
* Set authentication tokens in secure cookies
* @param {string} accessToken - JWT access token
* @param {boolean} rememberMe - Remember me flag
* @returns {Promise<void>}
*/
async setAuthTokens(accessToken, rememberMe = false) {
const now = new Date();
let accessTokenExpiry;
if (rememberMe) {
// 7 days for remember me
accessTokenExpiry = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
} else {
// 1 hour for session
accessTokenExpiry = new Date(now.getTime() + 60 * 60 * 1000);
}
// Set access token
await this.set('access_token', accessToken, {
expires: accessTokenExpiry,
secure: true,
sameSite: 'Strict'
});
// Store remember me preference
await this.set('remember_me', rememberMe.toString(), {
expires: accessTokenExpiry,
secure: true,
sameSite: 'Strict'
});
if (import.meta.env.VITE_NODE_ENV === 'development') {
console.log('πŸͺ [Cookie] Set auth tokens with rememberMe:', rememberMe);
}
}
/**
* Get authentication tokens
* @returns {Promise<Object|null>} - Auth tokens or null
*/
async getAuthTokens() {
try {
const [accessToken, rememberMe] = await Promise.all([
this.get('access_token'),
this.get('remember_me')
]);
if (!accessToken) {
return null;
}
return {
accessToken,
rememberMe: rememberMe === 'true'
};
} catch (error) {
console.error('πŸͺ [Cookie] Error getting auth tokens', error);
return null;
}
}
/**
* Clear authentication tokens
* @returns {Promise<void>}
*/
async clearAuthTokens() {
await Promise.all([
this.remove('access_token'),
this.remove('remember_me')
]);
if (import.meta.env.VITE_NODE_ENV === 'development') {
console.log('πŸͺ [Cookie] Cleared auth tokens');
}
}
/**
* Refresh authentication tokens
* @param {string} newAccessToken - New JWT access token
* @param {boolean} rememberMe - Remember me flag
* @returns {Promise<void>}
*/
async refreshAuthTokens(newAccessToken, rememberMe = false) {
await this.clearAuthTokens();
await this.setAuthTokens(newAccessToken, rememberMe);
}
}
// Export singleton instance
export default new CookieService();