Remove legacy references to global auth composable and document the new dependency injection pattern with single source of truth. Key Documentation Updates: • Update authentication.md with DI architecture details and usage patterns • Update chat integration docs to reference AuthService and remove legacy patterns • Add comprehensive authentication-architecture.md with full technical details • Document migration path from legacy global auth to current DI pattern Content Changes: • Replace useAuth.ts references with useAuthService.ts • Document AuthService as singleton with dependency injection • Add code examples for both component and service usage • Explain benefits of new architecture (single source of truth, no timing issues) • Update chat integration to reflect service-based architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
7 KiB
Authentication Architecture
This document describes the current authentication architecture using dependency injection and service-based patterns.
Overview
The authentication system has been refactored from a legacy global composable approach to a modern dependency injection pattern. This eliminates timing issues, improves maintainability, and provides a single source of truth for authentication state.
Architecture Components
1. AuthService (Core Service)
Location: src/modules/base/auth/auth-service.ts
The AuthService is a singleton service that extends BaseService and provides:
- Reactive authentication state management
- LNBits API integration
- Event-driven authentication lifecycle
- Automatic token management
Key Features:
export class AuthService extends BaseService {
// Public reactive state
public isAuthenticated = ref(false)
public user = ref<User | null>(null)
public isLoading = ref(false)
public error = ref<Error | null>(null)
// Computed properties for compatibility
public currentUser = computed(() => this.user.value)
public userDisplay = computed(() => { /* user display logic */ })
// Methods
async login(credentials: LoginCredentials): Promise<void>
async register(data: RegisterData): Promise<void>
async logout(): Promise<void>
async checkAuth(): Promise<boolean>
async updatePassword(current: string, newPass: string): Promise<void>
async updateProfile(data: Partial<User>): Promise<void>
}
2. useAuthService Composable (API Layer)
Location: src/composables/useAuthService.ts
The useAuthService composable provides a clean, unified API that bridges Vue components with the injected AuthService:
export function useAuth() {
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as AuthService
return {
// State (reactive refs from service)
currentUser: authService.currentUser,
isAuthenticated: authService.isAuthenticated,
isLoading: authService.isLoading,
error: authService.error,
userDisplay: authService.userDisplay,
// Methods (delegated to service)
initialize: () => authService.initialize(),
login: (credentials: any) => authService.login(credentials),
register: (data: any) => authService.register(data),
logout: () => authService.logout(),
checkAuth: () => authService.checkAuth()
}
}
3. Dependency Injection Container
Location: src/core/di-container.ts
The DI container manages service registration and injection:
// Service registration (in base module)
container.provide(SERVICE_TOKENS.AUTH_SERVICE, authService)
// Service consumption (in components/other services)
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as AuthService
Usage Patterns
In Vue Components (Recommended)
<script setup lang="ts">
import { useAuth } from '@/composables/useAuthService'
const auth = useAuth()
// Access reactive state
const isAuthenticated = auth.isAuthenticated
const currentUser = auth.currentUser
// Call methods
const handleLogin = async (credentials) => {
await auth.login(credentials)
}
</script>
In Services (Direct Injection)
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { AuthService } from '@/modules/base/auth/auth-service'
export class SomeService extends BaseService {
private get authService(): AuthService {
return this.dependencies.get('AuthService') as AuthService
}
async someMethod() {
const user = this.authService.user.value
if (user?.pubkey) {
// Use authenticated user data
}
}
}
Benefits of New Architecture
1. Single Source of Truth
- All authentication state managed by AuthService singleton
- No more timing issues between global and injected auth
- Consistent state across all modules and components
2. Dependency Injection Benefits
- Loose coupling between components and auth logic
- Easy to test (can inject mock services)
- Modular architecture with clear boundaries
- Service lifecycle management
3. Improved Maintainability
- Centralized authentication logic
- Event-driven architecture for auth state changes
- Clear separation of concerns
- Eliminated complex dual-auth detection patterns
4. Performance Improvements
- No duplicate service instances
- Efficient reactive state management
- Reduced complexity in components
Migration Guide
Before (Legacy Pattern)
// ❌ Old global composable pattern
import { auth } from '@/composables/useAuth'
// Direct access to global singleton
const isAuthenticated = auth.isAuthenticated.value
After (Current Pattern)
// ✅ New dependency injection pattern
import { useAuth } from '@/composables/useAuthService'
const auth = useAuth()
const isAuthenticated = auth.isAuthenticated.value
Service Integration
Module Registration
Each module that needs authentication access should:
- Declare dependency on AuthService in module metadata
- Access via dependency injection using the DI container
- Use reactive state from the service for UI updates
Example Service Integration
export class ChatService extends BaseService {
protected readonly metadata = {
name: 'ChatService',
dependencies: ['AuthService'] // Declare dependency
}
async sendMessage(content: string) {
// Access auth through DI
const user = this.authService.user.value
if (!user?.pubkey || !user?.prvkey) {
throw new Error('Authentication required for messaging')
}
// Use authenticated user data
const encrypted = await nip04.encrypt(user.prvkey, recipientPubkey, content)
// ... rest of implementation
}
}
Error Handling
The authentication system provides comprehensive error handling:
- Network Errors: Handled in AuthService with proper error propagation
- Invalid Credentials: Clear error messages passed to components
- Token Expiration: Automatic logout with cleanup
- Service Unavailable: Graceful degradation with retry logic
Testing
The dependency injection architecture makes testing straightforward:
// Mock the auth service for testing
const mockAuthService = {
isAuthenticated: ref(true),
currentUser: ref({ id: 'test-user', username: 'test' }),
login: vi.fn(),
logout: vi.fn()
}
// Inject mock in tests
container.provide(SERVICE_TOKENS.AUTH_SERVICE, mockAuthService)
Security Considerations
- Token Storage: Secure storage in localStorage with automatic cleanup
- API Integration: All requests authenticated via LNBits tokens
- State Management: Reactive state prevents stale authentication data
- Service Isolation: Authentication logic isolated in dedicated service
Future Enhancements
- Token Refresh: Automatic token refresh before expiration
- Multi-Factor Authentication: Support for additional auth factors
- Session Management: Enhanced session handling and persistence
- Audit Logging: Authentication event logging for security