package rfc8908 import ( "encoding/json" "net/http" "github.com/pkg/errors" ) type IsCaptiveFunc func(*http.Request) (bool, error) // RFC8098 // See https://tools.ietf.org/html/rfc8908 - "5. API State Structure" type Response struct { Captive bool `json:"captive"` UserPortalURL string `json:"user-portal-url,omitempty"` VenueInfoURL string `json:"venue-info-url,omitempty"` CanExtendSession *bool `json:"can-extend-session,omitempty"` SecondsRemaining *int `json:"seconds-remaining,omitempty"` BytesRemaining *int `json:"bytes-remaining,omitempty"` } // Handler returns a basic implementation of a Captive Portal API endpoint // as described in the IETF 8908 RFC (https://tools.ietf.org/html/rfc8908) func Handler(isCaptive IsCaptiveFunc, opts ...OptionsFunc) http.HandlerFunc { options := DefaultOptions() for _, o := range opts { o(options) } return func(w http.ResponseWriter, r *http.Request) { isCaptive, err := isCaptive(r) if err != nil { panic(errors.Wrap(err, "could not retrieve client id")) } ctx := r.Context() var bytesRemaining *int if options.BytesRemaining != nil { bytesRemaining, err = options.BytesRemaining(ctx) if err != nil { panic(errors.Wrap(err, "could not retrieve client remaining bytes")) } } var secondsRemaining *int if options.SecondsRemaining != nil { secondsRemaining, err = options.SecondsRemaining(ctx) if err != nil { panic(errors.Wrap(err, "could not retrieve client remaining seconds")) } } w.Header().Add("Content-Type", "application/captive+json") encoder := json.NewEncoder(w) res := &Response{ Captive: isCaptive, VenueInfoURL: options.VenueInfoURL, UserPortalURL: options.UserPortalURL, CanExtendSession: options.CanExtendSession, SecondsRemaining: secondsRemaining, BytesRemaining: bytesRemaining, } if err := encoder.Encode(res); err != nil { panic(errors.WithStack(err)) } } }