Files
upaas/internal/handlers/auth.go
clawbot f558e2cdd8
All checks were successful
Check / check (pull_request) Successful in 1m45s
feat: add observability improvements (metrics, audit log, structured logging)
- Add Prometheus metrics package (internal/metrics) with deployment,
  container health, webhook, HTTP request, and audit counters/histograms
- Add audit_log SQLite table via migration 007
- Add AuditEntry model with CRUD operations and query methods
- Add audit service (internal/service/audit) for recording user actions
- Instrument deploy service with deployment duration, count, and
  in-flight metrics; container health gauge updates on deploy completion
- Instrument webhook service with event counters by app/type/matched
- Instrument HTTP middleware with request count, duration, and response
  size metrics; also log response bytes in structured request logs
- Add audit logging to all key handler operations: login/logout, app
  CRUD, deploy, cancel, rollback, restart/stop/start, webhook receipt,
  and initial setup
- Add GET /api/audit endpoint for querying recent audit entries
- Make /metrics endpoint always available (optionally auth-protected)
- Add comprehensive tests for metrics, audit model, and audit service
- Update existing test infrastructure with metrics and audit dependencies
- Update README with Observability section documenting all metrics,
  audit log, and structured logging
2026-03-17 02:23:44 -07:00

86 lines
2.2 KiB
Go

package handlers
import (
"net/http"
"sneak.berlin/go/upaas/internal/models"
"sneak.berlin/go/upaas/templates"
)
// HandleLoginGET returns the login page handler.
func (h *Handlers) HandleLoginGET() http.HandlerFunc {
tmpl := templates.GetParsed()
return func(writer http.ResponseWriter, request *http.Request) {
data := h.addGlobals(map[string]any{}, request)
h.renderTemplate(writer, tmpl, "login.html", data)
}
}
// HandleLoginPOST handles the login form submission.
func (h *Handlers) HandleLoginPOST() http.HandlerFunc {
tmpl := templates.GetParsed()
return func(writer http.ResponseWriter, request *http.Request) {
parseErr := request.ParseForm()
if parseErr != nil {
http.Error(writer, "Bad Request", http.StatusBadRequest)
return
}
username := request.FormValue("username")
password := request.FormValue("password")
data := h.addGlobals(map[string]any{
"Username": username,
}, request)
if username == "" || password == "" {
data["Error"] = "Username and password are required"
h.renderTemplate(writer, tmpl, "login.html", data)
return
}
user, authErr := h.auth.Authenticate(request.Context(), username, password)
if authErr != nil {
data["Error"] = "Invalid username or password"
h.renderTemplate(writer, tmpl, "login.html", data)
return
}
sessionErr := h.auth.CreateSession(writer, request, user)
if sessionErr != nil {
h.log.Error("failed to create session", "error", sessionErr)
data["Error"] = "Failed to create session"
h.renderTemplate(writer, tmpl, "login.html", data)
return
}
h.auditLog(request, models.AuditActionLogin,
models.AuditResourceSession, "", "user logged in")
http.Redirect(writer, request, "/", http.StatusSeeOther)
}
}
// HandleLogout handles logout requests.
func (h *Handlers) HandleLogout() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
h.auditLog(request, models.AuditActionLogout,
models.AuditResourceSession, "", "user logged out")
destroyErr := h.auth.DestroySession(writer, request)
if destroyErr != nil {
h.log.Error("failed to destroy session", "error", destroyErr)
}
http.Redirect(writer, request, "/login", http.StatusSeeOther)
}
}