package server import ( "fmt" "log/slog" "net" "net/http" "strings" "ddc/internal/auth" ) type loginRequest struct { Username string `json:"username"` Password string `json:"password"` } type sessionResponse struct { Authenticated bool `json:"authenticated"` } func (server *Server) handleLogin(writer http.ResponseWriter, request *http.Request) { client := server.clientAddress(request) if !server.limiter.Allowed(client) { slog.Warn("login blocked after too many failures", "client", client, "request_id", requestIDFrom(request)) writeError(writer, http.StatusTooManyRequests, "too many failed attempts, try again later") return } var body loginRequest if !decodeBody(writer, request, &body) { return } if !auth.CredentialsMatch(server.settings.Username, server.settings.Password, body.Username, body.Password) { server.limiter.RecordFailure(client) slog.Warn("login failed", "client", client, "username", body.Username, "request_id", requestIDFrom(request)) writeError(writer, http.StatusUnauthorized, "invalid username or password") return } server.limiter.Reset(client) token, err := server.sessions.Create() if err != nil { writeFailure(writer, request, fmt.Errorf("could not create session: %w", err)) return } slog.Info("login succeeded", "client", client, "request_id", requestIDFrom(request)) http.SetCookie(writer, server.sessionCookie(token, int(server.settings.SessionTTL.Seconds()))) writeJSON(writer, http.StatusOK, sessionResponse{Authenticated: true}) } func (server *Server) handleLogout(writer http.ResponseWriter, request *http.Request) { if cookie, err := request.Cookie(sessionCookieName); err == nil { server.sessions.Delete(cookie.Value) } http.SetCookie(writer, server.sessionCookie("", -1)) writeJSON(writer, http.StatusOK, sessionResponse{Authenticated: false}) } func (server *Server) handleSession(writer http.ResponseWriter, request *http.Request) { writeJSON(writer, http.StatusOK, sessionResponse{Authenticated: server.authenticated(request)}) } func (server *Server) sessionCookie(value string, maxAge int) *http.Cookie { return &http.Cookie{ Name: sessionCookieName, Value: value, Path: "/", MaxAge: maxAge, HttpOnly: true, Secure: server.settings.CookieSecure, SameSite: http.SameSiteStrictMode, } } func (server *Server) clientAddress(request *http.Request) string { if server.settings.TrustProxyHeaders { if realIP := strings.TrimSpace(request.Header.Get("X-Real-IP")); realIP != "" { return realIP } } host, _, err := net.SplitHostPort(request.RemoteAddr) if err != nil { return request.RemoteAddr } return host }