Files
summercms/modules/lighthouse/centrifugo/handlers.go
Jakub Zych cada7a4442 feat(11-03): add the lighthouse realtime package and its Centrifugo driver
- lighthouse: Service/From with realtime.driver selection, RegisterDriver
  registry, null/log/memory drivers, Route/Surface/Mount, users and actors
- centrifugo: HTTP API client (apikey header, 2xx success, no request
  without a key), five-generator HS256 TokenIssuer, TokenHandler with the
  WinterCMS 401/503 bodies
- module README and root modules table row
2026-09-30 12:18:11 +02:00

68 lines
2.0 KiB
Go

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")))
}