Add CLI flags for setup and server configuration

- Add --setup flag to create admin user
- Add --admin-user and --admin-password for setup
- Generate cryptographically random passwords for --setup
- Add --bind and --port flags for server binding
- Add BIND env var support
- Add SMTP email service for password reset
- Add password reset token storage
- Add auth header to queue/logs frontend pages
- Fix journalctl timestamp format
- Fix logs to return empty array instead of error when no entries
- Fix queue handler to return proper error message
- Hide Users/Aliases nav links when no domain selected
This commit is contained in:
Christoph Haas 2026-03-22 01:17:43 +01:00
parent 2c3a4c3daa
commit 53ce3b06ae
14 changed files with 435 additions and 69 deletions

View file

@ -2,25 +2,29 @@ package handlers
import (
"encoding/json"
"log"
"net/http"
"strings"
"time"
"github.com/imc-vibe/backend/internal/auth"
"github.com/imc-vibe/backend/internal/db"
"github.com/imc-vibe/backend/internal/mail"
)
const MaxLoginAttempts = 5
type AuthHandler struct {
db *db.DB
jwtManager *auth.JWTManager
db *db.DB
jwtManager *auth.JWTManager
emailService *mail.EmailService
}
func NewAuthHandler(database *db.DB, jwtManager *auth.JWTManager) *AuthHandler {
func NewAuthHandler(database *db.DB, jwtManager *auth.JWTManager, emailService *mail.EmailService) *AuthHandler {
return &AuthHandler{
db: database,
jwtManager: jwtManager,
db: database,
jwtManager: jwtManager,
emailService: emailService,
}
}
@ -153,14 +157,29 @@ func (h *AuthHandler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
return
}
_, err := h.db.GetImcUserByUsername(req.Identifier)
if err == nil {
user, err := h.db.GetImcUserByUsername(req.Identifier)
if err != nil {
Success(w, map[string]string{
"message": "If the account exists, a password reset link will be sent",
})
return
}
token := mail.GenerateToken()
expiresAt := time.Now().Add(1 * time.Hour)
if err := h.db.CreatePasswordResetToken(user.ID, token, expiresAt); err != nil {
log.Printf("Failed to create password reset token: %v", err)
Error(w, http.StatusInternalServerError, "failed to create reset token")
return
}
if err := h.emailService.SendPasswordReset(user.Username, token); err != nil {
log.Printf("Failed to send password reset email: %v", err)
Error(w, http.StatusInternalServerError, "failed to send email")
return
}
Success(w, map[string]string{
"message": "If the account exists, a password reset link will be sent",
})

View file

@ -30,7 +30,7 @@ func (h *LogsHandler) List(w http.ResponseWriter, r *http.Request) {
entries, err := mail.GetLogs(hours, filter)
if err != nil {
Error(w, http.StatusInternalServerError, "failed to get logs")
Error(w, http.StatusServiceUnavailable, "log service not available")
return
}

View file

@ -20,7 +20,7 @@ func (h *QueueHandler) List(w http.ResponseWriter, r *http.Request) {
entries, err := mail.GetQueue()
if err != nil {
Error(w, http.StatusInternalServerError, "failed to get queue")
Error(w, http.StatusServiceUnavailable, "mail queue tools not available")
return
}

View file

@ -9,6 +9,7 @@ import (
"github.com/imc-vibe/backend/internal/auth"
"github.com/imc-vibe/backend/internal/config"
"github.com/imc-vibe/backend/internal/db"
"github.com/imc-vibe/backend/internal/mail"
)
type Router struct {
@ -25,9 +26,10 @@ type Router struct {
func New(database *db.DB, cfg *config.Config) *Router {
jwtManager := auth.NewJWTManager(cfg.JWTSecret, "imc-vibe")
emailService := mail.NewEmailService(cfg)
return &Router{
authHandler: handlers.NewAuthHandler(database, jwtManager),
authHandler: handlers.NewAuthHandler(database, jwtManager, emailService),
domainHandler: handlers.NewDomainHandler(database),
userHandler: handlers.NewUserHandler(database),
aliasHandler: handlers.NewAliasHandler(database),