farmcontrol-ui/src/utils/cookies.js

307 lines
7.8 KiB
JavaScript

// Cookie utility functions for authentication
const COOKIE_OPTIONS = {
path: '/',
secure: window.location.protocol === 'https:',
sameSite: 'Lax',
maxAge: 7 * 24 * 60 * 60 // 7 days in seconds
}
/**
* Set a cookie with authentication data
* @param {string} name - Cookie name
* @param {string} value - Cookie value
* @param {Object} options - Cookie options
*/
export const setCookie = (name, value, options = {}) => {
try {
if (!name || value === undefined || value === null) {
console.warn('Invalid cookie parameters:', { name, value })
return false
}
const cookieOptions = { ...COOKIE_OPTIONS, ...options }
let cookieString = `${name}=${encodeURIComponent(value)}`
if (cookieOptions.maxAge) {
cookieString += `; Max-Age=${cookieOptions.maxAge}`
}
if (cookieOptions.path) {
cookieString += `; Path=${cookieOptions.path}`
}
if (cookieOptions.domain) {
cookieString += `; Domain=${cookieOptions.domain}`
}
if (cookieOptions.secure) {
cookieString += '; Secure'
}
if (cookieOptions.sameSite) {
cookieString += `; SameSite=${cookieOptions.sameSite}`
}
if (cookieOptions.httpOnly) {
cookieString += '; HttpOnly'
}
document.cookie = cookieString
return true
} catch (error) {
console.error('Error setting cookie:', error)
return false
}
}
/**
* Get a cookie value by name
* @param {string} name - Cookie name
* @returns {string|null} - Cookie value or null if not found
*/
export const getCookie = (name) => {
try {
if (!name) {
console.warn('Cookie name is required')
return null
}
const nameEQ = name + '='
const cookies = document.cookie.split(';')
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i]
while (cookie.charAt(0) === ' ') {
cookie = cookie.substring(1, cookie.length)
}
if (cookie.indexOf(nameEQ) === 0) {
return decodeURIComponent(
cookie.substring(nameEQ.length, cookie.length)
)
}
}
return null
} catch (error) {
console.error('Error getting cookie:', error)
return null
}
}
/**
* Remove a cookie by setting it to expire in the past
* @param {string} name - Cookie name
* @param {Object} options - Cookie options (path and domain must match when setting)
*/
export const removeCookie = (name, options = {}) => {
try {
if (!name) {
console.warn('Cookie name is required for removal')
return false
}
const cookieOptions = { ...COOKIE_OPTIONS, ...options }
setCookie(name, '', { ...cookieOptions, maxAge: -1 })
return true
} catch (error) {
console.error('Error removing cookie:', error)
return false
}
}
/**
* Check if cookies are enabled in the browser
* @returns {boolean} - True if cookies are enabled
*/
export const areCookiesEnabled = () => {
try {
setCookie('test', 'test')
const enabled = getCookie('test') === 'test'
removeCookie('test')
return enabled
} catch (e) {
console.error('Error checking if cookies are enabled:', e)
return false
}
}
/**
* Check if authentication cookies are expired and clean them up if needed
* @returns {boolean} - True if cookies are valid and not expired
*/
export const validateAuthCookies = () => {
try {
const { token, expiresAt, user } = getAuthCookies()
if (!token || !expiresAt || !user) {
return false
}
const now = new Date()
const expirationDate = new Date(expiresAt)
if (expirationDate <= now) {
// Cookies are expired, clean them up
clearAuthCookies()
return false
}
return true
} catch (error) {
console.error('Error validating auth cookies:', error)
clearAuthCookies()
return false
}
}
/**
* Check if authentication cookies are about to expire (within specified minutes)
* @param {number} minutesBeforeExpiry - Minutes before expiry to consider "about to expire"
* @returns {Object} - Object with isExpiringSoon flag and timeRemaining in milliseconds
*/
export const checkAuthCookiesExpiry = (minutesBeforeExpiry = 5) => {
try {
const { token, expiresAt, user } = getAuthCookies()
if (!token || !expiresAt || !user) {
return { isExpiringSoon: false, timeRemaining: 0 }
}
const now = new Date()
const expirationDate = new Date(expiresAt)
const timeRemaining = expirationDate - now
const minutesRemaining = timeRemaining / (1000 * 60)
return {
isExpiringSoon: minutesRemaining <= minutesBeforeExpiry,
timeRemaining,
minutesRemaining: Math.floor(minutesRemaining)
}
} catch (error) {
console.error('Error checking auth cookies expiry:', error)
return { isExpiringSoon: false, timeRemaining: 0 }
}
}
/**
* Set up a listener for cookie changes to sync authentication state between tabs
* @param {Function} onAuthChange - Callback function when auth state changes
* @returns {Function} - Function to remove the listener
*/
export const setupCookieSync = (onAuthChange) => {
const handleStorageChange = (event) => {
// Check if auth-related cookies changed
if (
event.key === 'authToken' ||
event.key === 'authExpiresAt' ||
event.key === 'user'
) {
// Small delay to ensure cookies are updated
setTimeout(() => {
onAuthChange()
}, 100)
}
}
// Listen for storage events (for cross-tab communication)
window.addEventListener('storage', handleStorageChange)
// Also listen for cookie changes using a polling mechanism
let lastCookieState = document.cookie
const cookieCheckInterval = setInterval(() => {
const currentCookieState = document.cookie
if (currentCookieState !== lastCookieState) {
lastCookieState = currentCookieState
// Check if auth cookies changed
const authCookies = getAuthCookies()
if (authCookies.token || authCookies.expiresAt || authCookies.user) {
onAuthChange()
}
}
}, 1000)
// Return cleanup function
return () => {
window.removeEventListener('storage', handleStorageChange)
clearInterval(cookieCheckInterval)
}
}
/**
* Set authentication cookies
* @param {Object} authData - Authentication data object
* @returns {boolean} - True if all cookies were set successfully
*/
export const setAuthCookies = (authData) => {
try {
if (!authData) {
console.warn('Auth data is required')
return false
}
let success = true
if (authData.access_token) {
success =
success &&
setCookie('authToken', authData.access_token, {
maxAge: 7 * 24 * 60 * 60
}) // 7 days
}
if (authData.expires_at) {
success =
success &&
setCookie('authExpiresAt', authData.expires_at, {
maxAge: 7 * 24 * 60 * 60
})
}
if (authData.user) {
const userObject = {
...authData.user,
access_token: undefined,
refresh_token: undefined,
id_token: undefined
}
success =
success &&
setCookie('user', JSON.stringify(userObject), {
maxAge: 7 * 24 * 60 * 60
})
}
if (!success) {
console.warn('Some cookies failed to set, clearing all auth cookies')
clearAuthCookies()
}
return success
} catch (error) {
console.error('Error setting auth cookies:', error)
clearAuthCookies()
return false
}
}
/**
* Get authentication cookies
* @returns {Object} - Object containing auth data from cookies
*/
export const getAuthCookies = () => {
return {
token: getCookie('authToken'),
expiresAt: getCookie('authExpiresAt'),
user: getCookie('user') ? JSON.parse(getCookie('user')) : null
}
}
/**
* Clear authentication cookies
*/
export const clearAuthCookies = () => {
removeCookie('authToken')
removeCookie('authExpiresAt')
removeCookie('user')
}