File size: 6,733 Bytes
5d68d7e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | // 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' |