From e20f86e96a625920b6d2137b2724de264f0b96ef Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Wed, 26 Apr 2023 08:08:05 -0600 Subject: [PATCH] Adding token fetch ops --- bearer.go | 8 ++++++ go.mod | 3 +-- go.sum | 7 ++--- logic.go | 52 ++++++++++++++++++++++++++++++++++++++ middleware.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ oauth2_grants.go | 6 +++++ routes.go | 4 +-- 7 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 logic.go create mode 100644 middleware.go diff --git a/bearer.go b/bearer.go index 84eea50..9e20d19 100644 --- a/bearer.go +++ b/bearer.go @@ -8,6 +8,7 @@ import ( "time" "git.sr.ht/~sircmpwn/go-bare" + "hg.code.netlandish.com/~netlandish/gobwebs" "hg.code.netlandish.com/~netlandish/gobwebs/crypto" ) @@ -167,3 +168,10 @@ func (g *Grants) Has(grant string, mode string) bool { func (g *Grants) Encode() string { return g.encoded } + +// TokenUser wrapper for gobwebs.User and token grants +type TokenUser struct { + User gobwebs.User + Token *BearerToken + Grants *Grants +} diff --git a/go.mod b/go.mod index ad9c81a..be970c6 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/squirrel v1.5.4 github.com/labstack/echo/v4 v4.10.2 github.com/segmentio/ksuid v1.0.4 - hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230425200922-0a998d338bdd + hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230426140251-ec705d3e3fa6 ) require ( @@ -15,7 +15,6 @@ require ( github.com/99designs/gqlgen v0.17.29 // indirect github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f // indirect github.com/agnivade/levenshtein v1.1.1 // indirect - github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 // indirect github.com/alexedwards/scs/postgresstore v0.0.0-20211203064041-370cc303b69f // indirect github.com/alexedwards/scs/v2 v2.5.1 // indirect github.com/beorn7/perks v1.0.1 // indirect diff --git a/go.sum b/go.sum index c33fbc5..6b40d1f 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I= -github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc= github.com/alexedwards/scs/postgresstore v0.0.0-20211203064041-370cc303b69f h1:5jiSGWqKk8pJrjaN/KEANWe/4I767+d6FiKoDGpChik= github.com/alexedwards/scs/postgresstore v0.0.0-20211203064041-370cc303b69f/go.mod h1:TDDdV/xnjj+/4zBQ9a2k+i2AbuAdY7SQjPUh5zoTZ3M= github.com/alexedwards/scs/v2 v2.4.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= @@ -337,7 +335,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= @@ -630,8 +627,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230425200922-0a998d338bdd h1:l2Z28g5798LK5/jkAPozA6hBqKjHgOcNjJfU9fAoew4= -hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230425200922-0a998d338bdd/go.mod h1:+gStIFMqfW/W36PtW25x86MFS0FtXzbhnltOcB6hNf4= +hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230426140251-ec705d3e3fa6 h1:pd2wi4TjKWcPb13/SgTi8FSHSZjeVnPxbLC/F4k3I/4= +hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230426140251-ec705d3e3fa6/go.mod h1:+gStIFMqfW/W36PtW25x86MFS0FtXzbhnltOcB6hNf4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/logic.go b/logic.go new file mode 100644 index 0000000..1cfb22b --- /dev/null +++ b/logic.go @@ -0,0 +1,52 @@ +package oauth2 + +import ( + "context" + "crypto/sha512" + "encoding/hex" + "fmt" + "log" + + sq "github.com/Masterminds/squirrel" + "hg.code.netlandish.com/~netlandish/gobwebs" + "hg.code.netlandish.com/~netlandish/gobwebs/database" +) + +func OAuth2(ctx context.Context, token string, fetch gobwebs.UserFetch) (*TokenUser, error) { + bt := DecodeBearerToken(ctx, token) + if bt == nil { + return nil, fmt.Errorf("Invalid or expired OAuth 2.0 bearer token") + } + user, err := fetch.FromDB(ctx, uint(bt.UserID), true) + if err != nil { + return nil, err + } + + hash := sha512.Sum512([]byte(token)) + hashStr := hex.EncodeToString(hash[:]) + opts := &database.FilterOptions{ + Filter: sq.Eq{"hash": hashStr}, + } + grants, err := GetGrants(ctx, opts) + if err != nil { + return nil, err + } + if len(grants) == 0 { + return nil, fmt.Errorf("Invalid or expired OAuth 2.0 bearer token") + } else if len(grants) > 1 { + // Should never happen + log.Printf("Token hash %s has more than one grant record", hashStr) + return nil, fmt.Errorf("Error with provided OAuth 2.0 bearer token") + } + grant := grants[0] + if grant.IsExpired() { + return nil, fmt.Errorf("Invalid or expired OAuth 2.0 bearer token") + } + + gt := DecodeGrants(bt.Grants) + return &TokenUser{ + User: user, + Token: bt, + Grants: >, + }, nil +} diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..ec03aa8 --- /dev/null +++ b/middleware.go @@ -0,0 +1,66 @@ +package oauth2 + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/labstack/echo/v4" + "hg.code.netlandish.com/~netlandish/gobwebs" + "hg.code.netlandish.com/~netlandish/gobwebs/auth" + "hg.code.netlandish.com/~netlandish/gobwebs/server" +) + +var tuserCtxKey = &contextKey{"tokenuser"} + +type contextKey struct { + name string +} + +// Context adds TokenUser object to context for immediate use +func Context(ctx context.Context, tuser *TokenUser) context.Context { + return context.WithValue(ctx, tuserCtxKey, tuser) +} + +// ForContext pulls TokenUser value for context +func ForContext(ctx context.Context) *TokenUser { + tuser, ok := ctx.Value(tuserCtxKey).(*TokenUser) + if !ok { + panic(errors.New("invalid user context")) + } + return tuser +} + +// AuthorizationMiddleware ... +func AuthorizationMiddleware(fetch gobwebs.UserFetch) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + req := c.Request() + gctx := c.(*server.Context) + header := req.Header.Get("Authorization") + if header == "" { + z := strings.SplitN(header, " ", 2) + if len(z) != 2 { + return fmt.Errorf("Invalid Authorization header") + } + + switch strings.ToLower(z[0]) { + case "bearer": + token, err := OAuth2(req.Context(), z[1], fetch) + if err != nil { + return err + } + ctx := auth.Context(req.Context(), token.User) + ctx = Context(ctx, token) + c.SetRequest(c.Request().WithContext(ctx)) + gctx.User = token.User + return next(gctx) + default: + return fmt.Errorf("Invalid Authorization header") + } + } + return next(gctx) + } + } +} diff --git a/oauth2_grants.go b/oauth2_grants.go index 94b634d..2c93cb5 100644 --- a/oauth2_grants.go +++ b/oauth2_grants.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "time" sq "github.com/Masterminds/squirrel" "hg.code.netlandish.com/~netlandish/gobwebs/database" @@ -79,6 +80,11 @@ func (g *Grant) Store(ctx context.Context) error { return err } +// IsExpired will check the current grants exiration status +func (g *Grant) IsExpired() bool { + return time.Now().UTC().After(g.Expires.UTC()) +} + // Delete will delete this rate func (g *Grant) Delete(ctx context.Context) error { if g.ID == 0 { diff --git a/routes.go b/routes.go index 1b605b6..36efd7c 100644 --- a/routes.go +++ b/routes.go @@ -7,7 +7,7 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/labstack/echo/v4" "hg.code.netlandish.com/~netlandish/gobwebs" - "hg.code.netlandish.com/~netlandish/gobwebs/accounts" + "hg.code.netlandish.com/~netlandish/gobwebs/auth" "hg.code.netlandish.com/~netlandish/gobwebs/database" "hg.code.netlandish.com/~netlandish/gobwebs/server" ) @@ -21,7 +21,7 @@ type Service struct { // RegisterRoutes ... func (s *Service) RegisterRoutes() { - s.eg.Use(accounts.AuthRequired()) + s.eg.Use(auth.AuthRequired()) s.eg.GET("/clients", s.ListClients).Name = s.RouteName("list_clients") s.eg.GET("/clients/add", s.AddClient).Name = s.RouteName("add_client") s.eg.POST("/clients/add", s.AddClient).Name = s.RouteName("add_client_post") -- 2.45.2