Files
archivmail/internal/api/smtpout_handlers.go
T
sysops 92e57431c3 fix(PROJ-55): Tenant-Isolation für Rolle "auditor" + Audit-Log korrigieren
Kritischer Sicherheitsbug: Auditoren mit zugewiesenem Tenant sahen Mails
und Audit-Log-Einträge anderer Tenants (DSGVO-relevant). auditor wird
jetzt analog zu domain_auditor pro Tenant gescoped, sofern tenant_id
gesetzt ist (Abwärtskompatibilität: ohne tenant_id bleibt der bisherige
globale Zugriff erhalten). Betrifft Mail-Suche, Mail-Detailzugriff,
Export, eDiscovery, Threads, OCR sowie das Audit-Log (DB + tamper-evidentes
Flat-File), inkl. Befüllung von tenant_id an allen Audit-Log-Schreibstellen.
2026-06-21 22:26:06 +02:00

121 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"encoding/json"
"net/http"
"archivmail/config"
"archivmail/internal/audit"
"archivmail/internal/smtpoutconfig"
)
// handleGetSMTPOut returns the current SMTP-Out relay config (password masked).
// GET /api/admin/smtp-out — superadmin only.
func (s *Server) handleGetSMTPOut(w http.ResponseWriter, r *http.Request) {
cfg, err := s.smtpOutStore.Get(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
if cfg == nil {
writeJSON(w, http.StatusOK, map[string]interface{}{"configured": false})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{"configured": true, "config": cfg})
}
// handleSaveSMTPOut upserts the SMTP-Out relay config and reloads the mailer.
// PUT /api/admin/smtp-out — superadmin only.
func (s *Server) handleSaveSMTPOut(w http.ResponseWriter, r *http.Request) {
var body smtpoutconfig.SMTPOutConfig
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid body")
return
}
if body.Port == 0 {
body.Port = 587
}
sess := sessionFromCtx(r.Context())
if err := s.smtpOutStore.Save(r.Context(), body, sess.Username); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
// Reload the mailer at runtime — no restart needed
if full, err := s.smtpOutStore.GetWithPassword(r.Context()); err == nil && full != nil {
s.mailer.Reload(config.SMTPOutConfig{
Host: full.Host,
Port: full.Port,
User: full.User,
Password: full.Password,
TLS: full.TLS,
From: full.From,
})
}
if s.audlog != nil {
s.audlog.Log(audit.Entry{
EventType: "smtp_out_config_saved",
Username: sess.Username,
TenantID: sess.TenantID,
IPAddress: s.remoteIP(r),
Success: true,
})
}
writeJSON(w, http.StatusOK, map[string]interface{}{"ok": true})
}
// handleDeleteSMTPOut removes the SMTP-Out config and disables the mailer.
// DELETE /api/admin/smtp-out — superadmin only.
func (s *Server) handleDeleteSMTPOut(w http.ResponseWriter, r *http.Request) {
if err := s.smtpOutStore.Delete(r.Context()); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
// Disable mailer
s.mailer.Reload(config.SMTPOutConfig{})
sess := sessionFromCtx(r.Context())
if s.audlog != nil {
s.audlog.Log(audit.Entry{
EventType: "smtp_out_config_deleted",
Username: sess.Username,
TenantID: sess.TenantID,
IPAddress: s.remoteIP(r),
Success: true,
})
}
writeJSON(w, http.StatusOK, map[string]interface{}{"ok": true})
}
// handleTestSMTPOut sends a test email to the logged-in admin.
// POST /api/admin/smtp-out/test — superadmin only.
func (s *Server) handleTestSMTPOut(w http.ResponseWriter, r *http.Request) {
if !s.mailer.IsConfigured() {
writeError(w, http.StatusServiceUnavailable, "SMTP-Out nicht konfiguriert")
return
}
sess := sessionFromCtx(r.Context())
u, err := s.users.GetByUsername(sess.Username)
if err != nil || u.Email == "" {
writeError(w, http.StatusBadRequest, "Keine E-Mail-Adresse für diesen Account")
return
}
html := `<p>Dies ist eine Test-E-Mail von <strong>archivmail</strong>.<br>Der SMTP-Relay ist korrekt konfiguriert.</p>`
txt := "Dies ist eine Test-E-Mail von archivmail. Der SMTP-Relay ist korrekt konfiguriert."
if err := s.mailer.Send(u.Email, "archivmail SMTP-Test", html, txt); err != nil {
writeError(w, http.StatusBadGateway, "Versand fehlgeschlagen: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"ok": true,
"sent_to": u.Email,
})
}