From 1cc263ca8b898695b043fd9628c4e2b37caaf15e Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 2 Apr 2021 09:22:41 +0200 Subject: [PATCH] feat: add rfc8908 basic api endpoint implementation --- go.mod | 2 +- go.sum | 10 +++---- rfc8908/handler.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ rfc8908/option.go | 51 ++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 rfc8908/handler.go create mode 100644 rfc8908/option.go diff --git a/go.mod b/go.mod index bff82dc..4d5025b 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.15 require ( github.com/irai/arp v1.3.1 github.com/pkg/errors v0.9.1 - gitlab.com/wpetit/goweb v0.0.0-20201116180841-b73723b5f552 + gitlab.com/wpetit/goweb v0.0.0-20210329100650-576d5f8548a3 ) diff --git a/go.sum b/go.sum index f0b26cc..ee11486 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ= -cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= +cdr.dev/slog v1.4.0 h1:tLXQJ/hZ5Q051h0MBHSd2Ha8xzdXj7CjtzmG/8dUvUk= +cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -127,10 +127,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -gitlab.com/wpetit/goweb v0.0.0-20201116180841-b73723b5f552 h1:gr3i9XNCVMlFZi5E88j8qAUlOpvUidMbVn11ho4x7eI= -gitlab.com/wpetit/goweb v0.0.0-20201116180841-b73723b5f552/go.mod h1:Gfv7cBOw1T2XwXMsLm1d9kAjMAdNtLMjPv+yCzRO9qk= -gitlab.com/wpetit/goweb v0.0.0-20210329100043-1c740f83992f h1:SBORCOoEnJ/JTCS71not8Qg36LKvt2Kqk8/7BqE+93c= -gitlab.com/wpetit/goweb v0.0.0-20210329100043-1c740f83992f/go.mod h1:uLIkkm7YE+9mjPLzbCq2GkIiTRN9sJChrEAZjujpqv8= +gitlab.com/wpetit/goweb v0.0.0-20210329100650-576d5f8548a3 h1:9RxyXeV01/vjaAnJb9qbwM5Y+uJQB3XkcvtuxguuIWY= +gitlab.com/wpetit/goweb v0.0.0-20210329100650-576d5f8548a3/go.mod h1:4+weXRJy9RxDw7XnYGP3HrxNWaJhBDZKgiG1E+zRlqw= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= diff --git a/rfc8908/handler.go b/rfc8908/handler.go new file mode 100644 index 0000000..19d07c6 --- /dev/null +++ b/rfc8908/handler.go @@ -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)) + } + } +} diff --git a/rfc8908/option.go b/rfc8908/option.go new file mode 100644 index 0000000..c4a538c --- /dev/null +++ b/rfc8908/option.go @@ -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 + } +}