Files
archivmail/internal/api/smtpout_handlers.go
T
sysopsandClaude Sonnet 5 88cdc3eb3e fix(PROJ-73): Upload-Job-Status bei Panic + nil-Guards nach GetByUsername
Upload-Job bleibt bei einem Panic im Verarbeitungspfad nicht mehr auf
"running" hängen, sondern zeigt "error" mit generischer Meldung (Panic-
Rohwert nur im Server-Log, ErrMsg geht ans Frontend und könnte sonst
Mail-Inhalt-Fragmente transportieren). Zusätzlich fail-closed nil-Guards
an allen 13 GetByUsername-Aufrufstellen in internal/api/, die das
Ergebnis bisher ungeprüft dereferenzierten.

Verifiziert auf 192.168.1.132: Build und go vet fehlerfrei für die
geänderten Dateien.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019j28kGcaJAhBnrYX34hGdt
2026-08-05 13:50:36 +02:00

121 lines
3.6 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 == 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,
})
}