- 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
68 lines
2.0 KiB
Go
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")))
|
|
}
|