| |
| 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 |
| } |
|
|
| |
| const getAuthConfig = () => { |
| |
| const env = (import.meta as any).env |
| const usernameHash = env.VITE_AUTH_USERNAME_HASH |
| const passwordHash = env.VITE_AUTH_PASSWORD_HASH |
| |
| |
| 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" |
| } |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| 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() |
| } |
|
|
| |
| 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) |
| |
| |
| if (Date.now() < tokenData.expiresAt) { |
| this.updateState({ |
| user, |
| isAuthenticated: true, |
| isLoading: false |
| }) |
| return |
| } else { |
| |
| this.clearSession() |
| } |
| } |
| } catch (error) { |
| console.error('Error checking existing session:', error) |
| this.clearSession() |
| } |
| |
| this.updateState({ |
| user: null, |
| isAuthenticated: false, |
| isLoading: false |
| }) |
| } |
|
|
| |
| subscribe(callback: (authState: AuthState) => void) { |
| this.listeners.add(callback) |
| |
| callback(this.currentState) |
| |
| |
| return () => { |
| this.listeners.delete(callback) |
| } |
| } |
|
|
| |
| private updateState(newState: AuthState) { |
| this.currentState = newState |
| this.listeners.forEach(callback => callback(newState)) |
| } |
|
|
| |
| getState(): AuthState { |
| return this.currentState |
| } |
|
|
| |
| async login(credentials: LoginCredentials): Promise<{ success: boolean; error?: string }> { |
| try { |
| this.updateState({ ...this.currentState, isLoading: true }) |
|
|
| |
| await new Promise(resolve => setTimeout(resolve, 500)) |
|
|
| const hashedUsername = await hashInput(credentials.username) |
| const hashedPassword = await hashInput(credentials.password) |
|
|
| |
| const validCredentials = getAuthConfig() |
| const validUser = getUserConfig() |
|
|
| |
| if (hashedUsername === validCredentials.username && |
| hashedPassword === validCredentials.password) { |
| |
| |
| const now = Date.now() |
| const expiresAt = now + (TOKEN_EXPIRY_HOURS * 60 * 60 * 1000) |
| |
| const tokenData: TokenData = { |
| user: validUser, |
| timestamp: now, |
| expiresAt |
| } |
|
|
| |
| 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' } |
| } |
| } |
|
|
| |
| async logout(): Promise<void> { |
| this.clearSession() |
| this.updateState({ |
| user: null, |
| isAuthenticated: false, |
| isLoading: false |
| }) |
| } |
|
|
| |
| private clearSession() { |
| localStorage.removeItem(TOKEN_KEY) |
| localStorage.removeItem(USER_KEY) |
| } |
|
|
| |
| 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 |
| } |
| } |
| } |
|
|
| |
| export const authService = new AuthService() |
|
|
| |
| 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) |
| } |
| } |
|
|
| |
| import React from 'react' |