// Types export interface User { id: string name: string email: string } export interface AuthState { user: User | null isAuthenticated: boolean isLoading: boolean } export interface LoginCredentials { username: string password: string } // Environment-based configuration with fallbacks const getAuthConfig = () => { // Get credentials from environment variables using type assertion const env = (import.meta as any).env const usernameHash = env.VITE_AUTH_USERNAME_HASH const passwordHash = env.VITE_AUTH_PASSWORD_HASH // Validate that required environment variables are present if (!usernameHash || !passwordHash) { console.error('Missing required authentication environment variables!') console.error('Please set VITE_AUTH_USERNAME_HASH and VITE_AUTH_PASSWORD_HASH in your .env file') throw new Error('Authentication configuration missing. Check environment variables.') } return { username: usernameHash, password: passwordHash } } const getUserConfig = (): User => { const env = (import.meta as any).env return { id: env.VITE_AUTH_USER_ID || "1", name: env.VITE_AUTH_USER_NAME || "ACE Admin", email: env.VITE_AUTH_USER_EMAIL || "admin@aceui.com" } } // Helper function to hash input with SHA-256 using Web Crypto API (browser-compatible) async function hashInput(input: string): Promise { const encoder = new TextEncoder() const data = encoder.encode(input) const hashBuffer = await crypto.subtle.digest('SHA-256', data) const hashArray = Array.from(new Uint8Array(hashBuffer)) const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') return hashHex } // JWT-like token management (simplified for client-side) const TOKEN_KEY = 'ace_auth_token' const USER_KEY = 'ace_auth_user' const TOKEN_EXPIRY_HOURS = 24 interface TokenData { user: User timestamp: number expiresAt: number } class AuthService { private listeners: Set<(authState: AuthState) => void> = new Set() private currentState: AuthState = { user: null, isAuthenticated: false, isLoading: true } constructor() { this.checkExistingSession() } // Check for existing valid session on initialization private checkExistingSession() { try { const token = localStorage.getItem(TOKEN_KEY) const userData = localStorage.getItem(USER_KEY) if (token && userData) { const tokenData: TokenData = JSON.parse(token) const user: User = JSON.parse(userData) // Check if token is still valid if (Date.now() < tokenData.expiresAt) { this.updateState({ user, isAuthenticated: true, isLoading: false }) return } else { // Token expired, clear storage this.clearSession() } } } catch (error) { console.error('Error checking existing session:', error) this.clearSession() } this.updateState({ user: null, isAuthenticated: false, isLoading: false }) } // Subscribe to auth state changes subscribe(callback: (authState: AuthState) => void) { this.listeners.add(callback) // Immediately call with current state callback(this.currentState) // Return unsubscribe function return () => { this.listeners.delete(callback) } } // Update state and notify listeners private updateState(newState: AuthState) { this.currentState = newState this.listeners.forEach(callback => callback(newState)) } // Get current auth state getState(): AuthState { return this.currentState } // Login with credentials async login(credentials: LoginCredentials): Promise<{ success: boolean; error?: string }> { try { this.updateState({ ...this.currentState, isLoading: true }) // Simulate network delay for UX await new Promise(resolve => setTimeout(resolve, 500)) const hashedUsername = await hashInput(credentials.username) const hashedPassword = await hashInput(credentials.password) // Get auth configuration const validCredentials = getAuthConfig() const validUser = getUserConfig() // Validate credentials if (hashedUsername === validCredentials.username && hashedPassword === validCredentials.password) { // Create session token const now = Date.now() const expiresAt = now + (TOKEN_EXPIRY_HOURS * 60 * 60 * 1000) const tokenData: TokenData = { user: validUser, timestamp: now, expiresAt } // Store in localStorage localStorage.setItem(TOKEN_KEY, JSON.stringify(tokenData)) localStorage.setItem(USER_KEY, JSON.stringify(validUser)) this.updateState({ user: validUser, isAuthenticated: true, isLoading: false }) return { success: true } } else { this.updateState({ user: null, isAuthenticated: false, isLoading: false }) return { success: false, error: 'Invalid username or password' } } } catch (error) { console.error('Login error:', error) this.updateState({ user: null, isAuthenticated: false, isLoading: false }) return { success: false, error: 'An error occurred during login' } } } // Logout async logout(): Promise { this.clearSession() this.updateState({ user: null, isAuthenticated: false, isLoading: false }) } // Clear session data private clearSession() { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(USER_KEY) } // Check if session is valid isSessionValid(): boolean { try { const token = localStorage.getItem(TOKEN_KEY) if (!token) return false const tokenData: TokenData = JSON.parse(token) return Date.now() < tokenData.expiresAt } catch { return false } } } // Create singleton instance export const authService = new AuthService() // React hook for using auth service export function useAuth(): AuthState & { login: (credentials: LoginCredentials) => Promise<{ success: boolean; error?: string }> logout: () => Promise } { const [authState, setAuthState] = React.useState(authService.getState()) React.useEffect(() => { const unsubscribe = authService.subscribe(setAuthState) return unsubscribe }, []) return { ...authState, login: authService.login.bind(authService), logout: authService.logout.bind(authService) } } // React import for the hook import React from 'react'