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
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user