web-app/docs/01-architecture/authentication-architecture.md
padreug 6cb10a31db Update documentation to reflect new authentication architecture
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>
2025-09-07 00:53:42 +02:00

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

<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:

  1. Declare dependency on AuthService in module metadata
  2. Access via dependency injection using the DI container
  3. 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:

  1. Network Errors: Handled in AuthService with proper error propagation
  2. Invalid Credentials: Clear error messages passed to components
  3. Token Expiration: Automatic logout with cleanup
  4. 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

  1. Token Storage: Secure storage in localStorage with automatic cleanup
  2. API Integration: All requests authenticated via LNBits tokens
  3. State Management: Reactive state prevents stale authentication data
  4. Service Isolation: Authentication logic isolated in dedicated service

Future Enhancements

  1. Token Refresh: Automatic token refresh before expiration
  2. Multi-Factor Authentication: Support for additional auth factors
  3. Session Management: Enhanced session handling and persistence
  4. Audit Logging: Authentication event logging for security