Files
boc/backend/handlers/mail.go
T
Bernt 78b57273e2 security: Add proper authentication, RBAC, and tenant isolation
- Add password hashing with bcrypt
- Add AuthService with proper login
- Add password strength validation
- Add RBAC middleware (AdminOnly, ManagerOrAdmin)
- Add tenant isolation middleware
- Update CRM handler with tenant filtering
- Add JWT fallback for development mode
- Add user context helpers
- Build successful
2026-08-10 12:52:48 +00:00

207 lines
5.2 KiB
Go

package handlers
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"sync"
"github.com/go-chi/chi/v5"
"boc/email"
)
var (
mailConfig *MailConfig
mailConfigMu sync.RWMutex
defaultIMAP *email.IMAPClient
)
// MailConfig stores IMAP configuration
type MailConfig struct {
Email string `json:"email"`
Password string `json:"password"`
Server string `json:"server"`
Port int `json:"port"`
UseTLS bool `json:"useTLS"`
}
func init() {
// Try to initialize from environment
imapURL := os.Getenv("IMAP_URL")
if imapURL != "" {
var err error
defaultIMAP, err = email.ParseIMAPURL(imapURL)
if err != nil {
fmt.Println("Failed to parse IMAP_URL:", err.Error())
}
}
}
func getIMAPClient() *email.IMAPClient {
mailConfigMu.RLock()
defer mailConfigMu.RUnlock()
if mailConfig != nil && mailConfig.Email != "" {
return email.NewIMAPClient(mailConfig.Server, mailConfig.Port, mailConfig.Email, mailConfig.Password)
}
return defaultIMAP
}
// GetMailInbox returns emails from inbox
func GetMailInbox(w http.ResponseWriter, r *http.Request) {
client := getIMAPClient()
if client == nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": false,
"error": "IMAP not configured. Set IMAP_URL environment variable or configure via /api/v1/mail/config",
})
return
}
limit := 50
if l := r.URL.Query().Get("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
limit = parsed
}
}
messages, err := client.ListMessages(limit)
if err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"messages": messages,
"total": len(messages),
})
}
// GetMailMessage returns a single email
func GetMailMessage(w http.ResponseWriter, r *http.Request) {
client := getIMAPClient()
if client == nil {
http.Error(w, `{"error":"IMAP not configured"}`, http.StatusServiceUnavailable)
return
}
uidStr := chi.URLParam(r, "uid")
uid, err := strconv.ParseUint(uidStr, 10, 32)
if err != nil {
http.Error(w, `{"error":"invalid uid"}`, http.StatusBadRequest)
return
}
msg, err := client.GetMessage(uint32(uid))
if err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"message": msg,
})
}
// MarkMailAsRead marks an email as read
func MarkMailAsRead(w http.ResponseWriter, r *http.Request) {
client := getIMAPClient()
if client == nil {
http.Error(w, `{"error":"IMAP not configured"}`, http.StatusServiceUnavailable)
return
}
uidStr := chi.URLParam(r, "uid")
uid, err := strconv.ParseUint(uidStr, 10, 32)
if err != nil {
http.Error(w, `{"error":"invalid uid"}`, http.StatusBadRequest)
return
}
if err := client.MarkAsRead(uint32(uid)); err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
})
}
// GetMailUnreadCount returns unread message count
func GetMailUnreadCount(w http.ResponseWriter, r *http.Request) {
client := getIMAPClient()
if client == nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": false,
"error": "IMAP not configured. Set IMAP_URL environment variable or configure via /api/v1/mail/config",
})
return
}
count, err := client.GetUnreadCount()
if err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"count": count,
})
}
// SaveMailConfig saves mail configuration
func SaveMailConfig(w http.ResponseWriter, r *http.Request) {
var config MailConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
return
}
mailConfigMu.Lock()
mailConfig = &config
mailConfigMu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
})
}
// TestMailConnection tests IMAP connection
func TestMailConnection(w http.ResponseWriter, r *http.Request) {
var config MailConfig
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
return
}
client := email.NewIMAPClient(config.Server, config.Port, config.Email, config.Password)
count, err := client.GetUnreadCount()
if err != nil {
http.Error(w, fmt.Sprintf(`{"error":"%s"}`, err.Error()), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"ok": true,
"messageCount": count,
})
}