package auth import ( "fmt" "log" "sync" "time" ) // AuthService provides authentication functionality type AuthService struct { otpStore OTPStore smtpSender *SMTPSender sessionManager *SessionManager mu sync.RWMutex } // OTPStore defines the interface for storing and retrieving OTPs type OTPStore interface { StoreOTP(email, otp string, expiry time.Time) error GetOTP(email string) (string, time.Time, error) DeleteOTP(email string) error } // InMemoryOTPStore is a simple in-memory OTP store for development type InMemoryOTPStore struct { store map[string]struct { otp string expiry time.Time } mu sync.RWMutex } // NewInMemoryOTPStore creates a new in-memory OTP store func NewInMemoryOTPStore() *InMemoryOTPStore { return &InMemoryOTPStore{ store: make(map[string]struct { otp string expiry time.Time }), } } // StoreOTP stores an OTP for the given email func (s *InMemoryOTPStore) StoreOTP(email, otp string, expiry time.Time) error { s.mu.Lock() defer s.mu.Unlock() s.store[email] = struct { otp string expiry time.Time }{otp: otp, expiry: expiry} return nil } // GetOTP retrieves the OTP for the given email func (s *InMemoryOTPStore) GetOTP(email string) (string, time.Time, error) { s.mu.RLock() defer s.mu.RUnlock() entry, exists := s.store[email] if !exists { return "", time.Time{}, fmt.Errorf("OTP not found for email: %s", email) } return entry.otp, entry.expiry, nil } // DeleteOTP removes the OTP for the given email func (s *InMemoryOTPStore) DeleteOTP(email string) error { s.mu.Lock() defer s.mu.Unlock() delete(s.store, email) return nil } // NewAuthService creates a new authentication service func NewAuthService(smtpSender *SMTPSender, sessionManager *SessionManager, otpStore OTPStore) *AuthService { return &AuthService{ otpStore: otpStore, smtpSender: smtpSender, sessionManager: sessionManager, } } // RequestOTP generates and sends an OTP to the given email func (as *AuthService) RequestOTP(email string) error { // Generate OTP otpPlain, otp, err := GenerateOTP() if err != nil { return fmt.Errorf("failed to generate OTP: %w", err) } // Store OTP hash and expiry err = as.otpStore.StoreOTP(email, otp.Hash, otp.Expiry) if err != nil { return fmt.Errorf("failed to store OTP: %w", err) } // Send OTP via email err = as.smtpSender.SendOTP(email, otpPlain) if err != nil { log.Printf("OTP for %s: %s (SMTP failed: %v)", email, otpPlain, err) // Keep the OTP stored so developer can use it for testing return nil // Don't fail — allow dev access via logs } log.Printf("OTP sent to %s", email) return nil } // VerifyOTP verifies the provided OTP for the given email func (as *AuthService) VerifyOTP(email, userOTP string) (bool, error) { // Retrieve stored OTP storedOTPHash, expiry, err := as.otpStore.GetOTP(email) if err != nil { return false, fmt.Errorf("OTP not found or expired: %w", err) } // Create OTP struct for verification storedOTP := &OTP{ Hash: storedOTPHash, Expiry: expiry, } // Verify OTP if !VerifyOTP(userOTP, storedOTP) { return false, nil } // Clean up OTP after successful verification as.otpStore.DeleteOTP(email) return true, nil } // GetSessionManager returns the session manager func (as *AuthService) GetSessionManager() *SessionManager { return as.sessionManager }