Spaces:
Sleeping
Sleeping
| // 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<string> { | |
| 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<void> { | |
| 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<void> | |
| } { | |
| const [authState, setAuthState] = React.useState<AuthState>(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' |