package api import ( "encoding/json" "net/http" "archivdms/internal/audit" ) type loginRequest struct { Username string `json:"username"` Password string `json:"password"` } func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { s.reqLog(ctx).Warn("login: invalid request body", "remote_ip", s.remoteIP(r), "err", err) writeError(w, http.StatusBadRequest, "invalid request body") return } ip := s.remoteIP(r) token, user, err := s.authMgr.LoginFrom(ctx, req.Username, req.Password, ip) if err != nil { // Kein Passwort, keine Fehlerdetails vom Auth-Manager im Klartext: // nur Benutzername + IP zur Korrelation von Brute-Force-Versuchen. s.reqLog(ctx).Warn("login failed", "username", req.Username, "remote_ip", ip, "reason", "invalid_credentials") s.audlog.Log(audit.Entry{ EventType: audit.EventLogin, Username: req.Username, IPAddress: ip, Success: false, Detail: "invalid_credentials", }) writeError(w, http.StatusUnauthorized, "invalid credentials") return } http.SetCookie(w, &http.Cookie{ Name: sessionCookieName, Value: token, Path: "/", HttpOnly: true, Secure: s.cfg.SecureCookies, SameSite: http.SameSiteLaxMode, MaxAge: 8 * 60 * 60, }) if err := s.users.UpdateLastLogin(user.ID); err != nil { s.reqLog(ctx).Warn("login: last_login update failed", "user_id", user.ID, "err", err) } s.reqLog(ctx).Info("login succeeded", "user_id", user.ID, "username", user.Username, "tenant_id", user.TenantID, "remote_ip", ip) s.audlog.Log(audit.Entry{ EventType: audit.EventLogin, Username: user.Username, IPAddress: ip, TenantID: user.TenantID, Success: true, }) writeJSON(w, http.StatusOK, map[string]any{"token": token, "user": user}) } func (s *Server) handleMe(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sess := sessionFromCtx(ctx) user, err := s.users.GetByID(sess.UserID) if err != nil { s.reqLog(ctx).Warn("me: user lookup failed", "user_id", sess.UserID, "err", err) writeError(w, http.StatusNotFound, "user not found") return } writeJSON(w, http.StatusOK, user) } func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sess := sessionFromCtx(ctx) token := "" if c, err := r.Cookie(sessionCookieName); err == nil { token = c.Value } if token == "" { token = extractBearerToken(r) } if token != "" { if err := s.authMgr.Logout(token); err != nil { s.reqLog(ctx).Warn("logout: session invalidation failed", "user_id", sess.UserID, "err", err) } } s.reqLog(ctx).Info("logout", "user_id", sess.UserID, "username", sess.Username, "tenant_id", sess.TenantID, "remote_ip", s.remoteIP(r)) http.SetCookie(w, &http.Cookie{ Name: sessionCookieName, Value: "", Path: "/", HttpOnly: true, MaxAge: -1, }) s.audlog.Log(audit.Entry{ EventType: audit.EventLogout, Username: sess.Username, IPAddress: s.remoteIP(r), TenantID: sess.TenantID, Success: true, }) writeJSON(w, http.StatusOK, map[string]string{"status": "logged out"}) }