- Fixed settings form silently dropping fields (multipart/form-data parse) - Fixed IMAP test connection (multipart parse + raw field logging) - Added IMAPUsername field throughout (model, settings, handlers, worker) - Replaced smtp.SendMail with custom sendMail (explicit HELO + STARTTLS) - Added From header to OTP/Welcome emails (RFC5322 compliance) - Worker now processes ALL INBOX emails (FetchBatch instead of FetchUnseen) - Fixed go build . compiling debug scripts instead of src/cmd/main.go - Added Notifications, Finance, Social classification folders (7 total) - Refined AI prompt with precise category descriptions - Added session guardrail to AGENTS.md
141 lines
No EOL
3.3 KiB
Go
141 lines
No EOL
3.3 KiB
Go
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
|
|
} |