feat: add rfc8908 basic api endpoint implementation

This commit is contained in:
2021-04-02 09:22:41 +02:00
parent e8b8bbd952
commit 1cc263ca8b
4 changed files with 128 additions and 7 deletions

72
rfc8908/handler.go Normal file
View File

@ -0,0 +1,72 @@
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))
}
}
}

51
rfc8908/option.go Normal file
View File

@ -0,0 +1,51 @@
package rfc8908
import "context"
type Options struct {
UserPortalURL string
VenueInfoURL string
CanExtendSession *bool
SecondsRemaining SecondsRemainingFunc
BytesRemaining BytesRemainingFunc
}
type OptionsFunc func(*Options)
type SecondsRemainingFunc func(context.Context) (*int, error)
type BytesRemainingFunc func(context.Context) (*int, error)
func DefaultOptions() *Options {
return &Options{}
}
func WithUserPortalURL(userPortalURL string) OptionsFunc {
return func(o *Options) {
o.UserPortalURL = userPortalURL
}
}
func WithVenueInfoURL(venueInfoURL string) OptionsFunc {
return func(o *Options) {
o.VenueInfoURL = venueInfoURL
}
}
func WithCanExtendSession(canExtend bool) OptionsFunc {
return func(o *Options) {
o.CanExtendSession = &canExtend
}
}
func WithSecondsRemainingFunc(fn SecondsRemainingFunc) OptionsFunc {
return func(o *Options) {
o.SecondsRemaining = fn
}
}
func WithBytesRemainingFunc(fn BytesRemainingFunc) OptionsFunc {
return func(o *Options) {
o.BytesRemaining = fn
}
}