package centrifugo import ( "bytes" "encoding/json" "net/http" "git.golem15.com/golem15/summercms/modules/bouncer" "git.golem15.com/golem15/summercms/modules/lighthouse" ) type errorBody struct { Error string `json:"error"` } type tokenBody struct { Token string `json:"token"` } // TokenHandler issues the connection token of the signed-in user. It must // be mounted behind a user guard (the UserAuth surface). // // - no principal, or no user for it: 401 {"error":"Unauthorized"} // - no token secret: 503 with the WinterCMS not-configured error body // - otherwise 200 {"token":"…"} (see TokenIssuer.ForUser) func TokenHandler(svc *lighthouse.Service, issuer *TokenIssuer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { p, ok := bouncer.User(r.Context()) if !ok || p == nil || p.ID == 0 { writeJSON(w, http.StatusUnauthorized, errorBody{Error: "Unauthorized"}) return } u, found, err := svc.User(r.Context(), p.ID) if err != nil || !found { writeJSON(w, http.StatusUnauthorized, errorBody{Error: "Unauthorized"}) return } if !issuer.Configured() { writeJSON(w, http.StatusServiceUnavailable, errorBody{Error: "WebSocket not configured"}) return } tok, err := issuer.ForUser(u) if err != nil { svc.Logger().Error("realtime: token signing failed", "error", err) writeJSON(w, http.StatusInternalServerError, errorBody{Error: "Internal server error"}) return } writeJSON(w, http.StatusOK, tokenBody{Token: tok}) } } // writeJSON writes v with no trailing newline and no HTML escaping, plus the // Content-Type and Cache-Control headers of a Laravel JSON response. func writeJSON(w http.ResponseWriter, status int, v any) { var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetEscapeHTML(false) if err := enc.Encode(v); err != nil { w.WriteHeader(http.StatusInternalServerError) return } h := w.Header() h.Set("Content-Type", "application/json") h.Set("Cache-Control", "no-cache, private") w.WriteHeader(status) _, _ = w.Write(bytes.TrimSuffix(buf.Bytes(), []byte("\n"))) }