M bearer.go => bearer.go +8 -0
@@ 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
+}
M go.mod => go.mod +1 -2
@@ 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
M go.sum => go.sum +2 -5
@@ 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=
A logic.go => logic.go +52 -0
@@ 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
+}
A middleware.go => middleware.go +66 -0
@@ 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)
+ }
+ }
+}
M oauth2_grants.go => oauth2_grants.go +6 -0
@@ 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 {
M routes.go => routes.go +2 -2
@@ 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")