M bearer.go => bearer.go +5 -3
@@ 31,6 31,7 @@ func ToTimestamp(t time.Time) Timestamp {
// BearerToken ...
type BearerToken struct {
Version uint
+ Issued Timestamp
Expires Timestamp
Grants string
ClientID string
@@ 171,7 172,8 @@ func (g *Grants) Encode() string {
// TokenUser wrapper for gobwebs.User and token grants
type TokenUser struct {
- User gobwebs.User
- Token *BearerToken
- Grants *Grants
+ User gobwebs.User
+ Token *BearerToken
+ Grants *Grants
+ TokenHash [64]byte
}
M go.mod => go.mod +1 -1
@@ 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-20230426140251-ec705d3e3fa6
+ hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230428122420-009ed0d86738
)
require (
M go.sum => go.sum +2 -2
@@ 627,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-20230426140251-ec705d3e3fa6 h1:pd2wi4TjKWcPb13/SgTi8FSHSZjeVnPxbLC/F4k3I/4=
-hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230426140251-ec705d3e3fa6/go.mod h1:+gStIFMqfW/W36PtW25x86MFS0FtXzbhnltOcB6hNf4=
+hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230428122420-009ed0d86738 h1:y3ywBE43YyBWjV32gHyfJArK6Xj89damOXDzWUTZ8AQ=
+hg.code.netlandish.com/~netlandish/gobwebs v0.0.0-20230428122420-009ed0d86738/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=
M logic.go => logic.go +5 -3
@@ 43,10 43,12 @@ func OAuth2(ctx context.Context, token string, fetch gobwebs.UserFetch) (*TokenU
return nil, fmt.Errorf("Invalid or expired OAuth 2.0 bearer token")
}
+ bt.Issued = ToTimestamp(grant.Issued)
gt := DecodeGrants(bt.Grants)
return &TokenUser{
- User: user,
- Token: bt,
- Grants: >,
+ User: user,
+ Token: bt,
+ Grants: >,
+ TokenHash: hash,
}, nil
}
M middleware.go => middleware.go +1 -2
@@ 2,7 2,6 @@ package oauth2
import (
"context"
- "errors"
"fmt"
"strings"
@@ 27,7 26,7 @@ func Context(ctx context.Context, tuser *TokenUser) context.Context {
func ForContext(ctx context.Context) *TokenUser {
tuser, ok := ctx.Value(tuserCtxKey).(*TokenUser)
if !ok {
- panic(errors.New("invalid user context"))
+ return nil
}
return tuser
}
M routes.go => routes.go +49 -0
@@ 21,6 21,8 @@ type Service struct {
// RegisterRoutes ...
func (s *Service) RegisterRoutes() {
+ s.eg.POST("/introspect", s.Introspect).Name = s.RouteName("introspect_post")
+
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")
@@ 79,6 81,53 @@ func (s *Service) AddClient(c echo.Context) error {
return gctx.Render(http.StatusOK, "oauth2_add_client.html", gmap)
}
+// Introspect ...
+func (s *Service) Introspect(c echo.Context) error {
+ req := c.Request()
+ ctype := req.Header.Get("Content-Type")
+ if ctype != "application/x-www-form-urlencoded" {
+ retErr := struct {
+ err string `json:"error"`
+ desc string `json:"error_description"`
+ uri string `json:"error_url"` // TODO Make this customizable
+ }{
+ err: "invalid request",
+ desc: "Content-Type must be application/x-www-form-urlencoded",
+ }
+ return c.JSON(http.StatusBadRequest, &retErr)
+ }
+
+ retFalse := struct {
+ active bool `json:"active"`
+ }{false}
+
+ token := ForContext(req.Context())
+ if token == nil {
+ return c.JSON(http.StatusOK, retFalse)
+ }
+
+ if token.Token.ClientID == "" {
+ return c.JSON(http.StatusOK, retFalse)
+ }
+
+ ret := struct {
+ active bool `json:"active"`
+ clientID string `json:"client_id"`
+ username string `json:"username"`
+ tokenType string `json:"token_type"`
+ exp int `json:"exp"`
+ iat int `json:"iat"`
+ }{
+ active: true,
+ clientID: token.Token.ClientID,
+ username: token.User.GetEmail(),
+ tokenType: "bearer",
+ exp: int(token.Token.Expires),
+ iat: int(token.Token.Issued),
+ }
+ return c.JSON(http.StatusOK, ret)
+}
+
// RouteName ...
func (s *Service) RouteName(value string) string {
return fmt.Sprintf("%s:%s", s.name, value)