~netlandish/gobwebs-oauth2

a54d2dc13b2acb8baf44a19439a958f0caeb130e — Peter Sanchez 1 year, 6 months ago 4643c93
Adding introspect handler
6 files changed, 63 insertions(+), 11 deletions(-)

M bearer.go
M go.mod
M go.sum
M logic.go
M middleware.go
M routes.go
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: &gt,
		User:      user,
		Token:     bt,
		Grants:    &gt,
		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)