Refactor authentication architecture to eliminate dual auth complexity

This major refactor consolidates the authentication system to use a single
source of truth, eliminating timing issues and architectural complexity
that was causing chat and payment functionality problems.

Key Changes:
• Remove old global useAuth composable and replace with useAuthService wrapper
• Update all 25+ files to use consistent auth pattern via dependency injection
• Eliminate dual auth detection workarounds from services (ChatService, PaymentService, etc.)
• Fix TypeScript errors and add proper Uint8Array conversion for Nostr private keys
• Consolidate auth state management to AuthService as single source of truth

Benefits:
• Resolves chat peer loading and message subscription timing issues
• Fixes wallet detection problems for Lightning payments
• Eliminates race conditions between global and injected auth
• Maintains API compatibility while improving architecture
• Reduces code complexity and improves maintainability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-09-07 00:47:02 +02:00
parent 5633aa154b
commit 4feb5459cc
27 changed files with 210 additions and 518 deletions

View file

@ -1,168 +0,0 @@
import { ref, computed } from 'vue'
import { lnbitsAPI, type User, type LoginCredentials, type RegisterData } from '@/lib/api/lnbits'
import { useMultiAsyncOperation } from '@/core/composables/useAsyncOperation'
const currentUser = ref<User | null>(null)
// Shared async operations for auth actions
const authOperations = useMultiAsyncOperation<{
initialize: User | null
login: User
register: User
logout: void
}>()
export function useAuth() {
const isAuthenticated = computed(() => !!currentUser.value)
// Get operation instances
const initializeOp = authOperations.createOperation('initialize')
const loginOp = authOperations.createOperation('login')
const registerOp = authOperations.createOperation('register')
const logoutOp = authOperations.createOperation('logout')
/**
* Initialize authentication on app start
*/
async function initialize(): Promise<void> {
try {
await initializeOp.execute(async () => {
if (lnbitsAPI.isAuthenticated()) {
const user = await lnbitsAPI.getCurrentUser()
currentUser.value = user
return user
}
return null
}, {
errorMessage: 'Failed to initialize authentication',
showToast: false // Don't show toast for initialization errors
})
} catch {
// Clear invalid token on error
await logout()
}
}
/**
* Login with username and password
*/
async function login(credentials: LoginCredentials): Promise<void> {
await loginOp.execute(async () => {
await lnbitsAPI.login(credentials)
// Get user details
const user = await lnbitsAPI.getCurrentUser()
currentUser.value = user
return user
}, {
errorMessage: 'Login failed'
})
}
/**
* Register new user
*/
async function register(data: RegisterData): Promise<void> {
await registerOp.execute(async () => {
await lnbitsAPI.register(data)
// Get user details
const user = await lnbitsAPI.getCurrentUser()
currentUser.value = user
return user
}, {
errorMessage: 'Registration failed'
})
}
/**
* Logout and clear user data
*/
async function logout(): Promise<void> {
await logoutOp.execute(async () => {
// Clear local state
lnbitsAPI.logout()
currentUser.value = null
// Clear all auth operation states
authOperations.clearAll()
}, {
showToast: false // Don't show toast for logout
})
}
/**
* Update user password
*/
async function updatePassword(currentPassword: string, newPassword: string): Promise<void> {
const updatePasswordOp = authOperations.createOperation('updatePassword' as any)
return await updatePasswordOp.execute(async () => {
const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword)
currentUser.value = updatedUser
return updatedUser
}, {
errorMessage: 'Failed to update password'
})
}
/**
* Update user profile
*/
async function updateProfile(data: Partial<User>): Promise<void> {
const updateProfileOp = authOperations.createOperation('updateProfile' as any)
return await updateProfileOp.execute(async () => {
const updatedUser = await lnbitsAPI.updateProfile(data)
currentUser.value = updatedUser
return updatedUser
}, {
errorMessage: 'Failed to update profile'
})
}
/**
* Check if user is authenticated
*/
function checkAuth(): boolean {
return lnbitsAPI.isAuthenticated()
}
/**
* Get user display info
*/
const userDisplay = computed(() => {
if (!currentUser.value) return null
return {
name: currentUser.value.username || currentUser.value.email || 'Anonymous',
username: currentUser.value.username,
email: currentUser.value.email,
id: currentUser.value.id,
shortId: currentUser.value.id.slice(0, 8) + '...' + currentUser.value.id.slice(-8)
}
})
return {
// State
currentUser: computed(() => currentUser.value),
isAuthenticated,
isLoading: computed(() => authOperations.isAnyLoading()),
error: computed(() => authOperations.hasAnyError() ?
(initializeOp.error.value || loginOp.error.value || registerOp.error.value || logoutOp.error.value) : null),
userDisplay,
// Actions
initialize,
login,
register,
logout,
updatePassword,
updateProfile,
checkAuth
}
}
// Export singleton instance for global state
export const auth = useAuth()

View file

@ -0,0 +1,72 @@
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { AuthService } from '@/modules/base/auth/auth-service'
/**
* Composable to access the injected auth service
* This replaces the global auth composable with the injected service
*/
export function useAuth() {
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as AuthService
if (!authService) {
throw new Error('AuthService not available. Make sure base module is installed.')
}
return {
// State
currentUser: authService.currentUser,
isAuthenticated: authService.isAuthenticated,
isLoading: authService.isLoading,
error: authService.error,
userDisplay: authService.userDisplay,
// Compatibility aliases
user: authService.user,
// Actions
initialize: () => authService.initialize(),
login: (credentials: any) => authService.login(credentials),
register: (data: any) => authService.register(data),
logout: () => authService.logout(),
updatePassword: (current: string, newPass: string) => authService.updatePassword(current, newPass),
updateProfile: (data: any) => authService.updateProfile(data),
checkAuth: () => authService.checkAuth(),
refresh: () => authService.refresh()
}
}
// Export singleton reference for compatibility
export function getAuthService() {
return injectService(SERVICE_TOKENS.AUTH_SERVICE) as AuthService
}
// For files that import { auth } directly
export const auth = {
get currentUser() {
const service = getAuthService()
return service?.currentUser
},
get isAuthenticated() {
const service = getAuthService()
return service?.isAuthenticated
},
get isLoading() {
const service = getAuthService()
return service?.isLoading
},
get error() {
const service = getAuthService()
return service?.error
},
get userDisplay() {
const service = getAuthService()
return service?.userDisplay
},
initialize: () => getAuthService()?.initialize(),
login: (credentials: any) => getAuthService()?.login(credentials),
register: (data: any) => getAuthService()?.register(data),
logout: () => getAuthService()?.logout(),
updatePassword: (current: string, newPass: string) => getAuthService()?.updatePassword(current, newPass),
updateProfile: (data: any) => getAuthService()?.updateProfile(data),
checkAuth: () => getAuthService()?.checkAuth()
}