~netlandish/gobwebs-oauth2

6ad3ba8aa67925c52a50a7de34e55d198b4f068a — Peter Sanchez 1 year, 7 months ago 33b8876
Adding client token management handlers
2 files changed, 149 insertions(+), 1 deletions(-)

M clients.go
M routes.go
M clients.go => clients.go +29 -0
@@ 145,6 145,35 @@ func (c *Client) VerifyClientSecret(clientSecret string) bool {

}

// Revoke will revoke all grants for a given client
func (c *Client) Revoke(ctx context.Context) error {
	if c.ID == 0 {
		return fmt.Errorf("Client object is not populated")
	}
	err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
		c.Revoked = true
		err := sq.
			Update("oauth2_clients").
			Set("revoked", c.Revoked).
			Where("id = ?", c.ID).
			Suffix(`RETURNING (updated_on)`).
			PlaceholderFormat(sq.Dollar).
			RunWith(tx).
			ScanContext(ctx, &c.UpdatedOn)
		if err != nil {
			return err
		}

		_, err = tx.ExecContext(ctx, `
			UPDATE oauth2_grants
			SET expires = NOW() AT TIME ZONE 'UTC'
			WHERE client_id = $1;
		`, c.ID)
		return err
	})
	return err
}

// Delete will delete this client
func (c *Client) Delete(ctx context.Context) error {
	if c.ID == 0 {

M routes.go => routes.go +120 -1
@@ 10,6 10,7 @@ import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"



@@ 44,9 45,14 @@ func (s *Service) RegisterRoutes() {
	s.eg.GET("/personal", s.ListPersonal).Name = s.RouteName("list_personal")
	s.eg.GET("/personal/add", s.AddPersonal).Name = s.RouteName("add_personal")
	s.eg.POST("/personal/add", s.AddPersonal).Name = s.RouteName("add_personal_post")
	s.eg.GET("/personal/:id/revoke", s.RevokePersonal).Name = s.RouteName("revoke_personal")
	s.eg.POST("/personal/:id/revoke", s.RevokePersonal).Name = s.RouteName("revoke_personal_post")
	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")
	s.eg.GET("/clients/:id", s.DetailClient).Name = s.RouteName("detail_client")
	s.eg.POST("/clients/:id/unregister", s.UnregisterClient).Name = s.RouteName("unregister_client_post")
	s.eg.POST("/clients/:id/reissue", s.ReissueClient).Name = s.RouteName("reissue_client_post")
	s.eg.GET("/authorize", s.Authorize).Name = s.RouteName("authorize")
	s.eg.POST("/authorize", s.AuthorizePOST).Name = s.RouteName("authorize_post")
}


@@ 118,11 124,53 @@ func (s *Service) AddPersonal(c echo.Context) error {
	return gctx.Render(http.StatusOK, "oauth2_add_personal.html", gmap)
}

// RevokePersonal ...
func (s *Service) RevokePersonal(c echo.Context) error {
	id, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		return echo.NotFoundHandler(c)
	}
	gctx := c.(*server.Context)
	opts := &database.FilterOptions{
		Filter: sq.And{
			sq.Eq{"id": id},
			sq.Eq{"user_id": gctx.User.GetID()},
			sq.Expr("client_id IS NULL"),
			sq.Expr("expires > NOW() at time zone 'UTC'"),
		},
	}
	tokens, err := GetGrants(c.Request().Context(), opts)
	if err != nil {
		return err
	}
	if len(tokens) == 0 {
		return echo.NotFoundHandler(c)
	}
	token := tokens[0]

	req := c.Request()
	if req.Method == "POST" {
		if err := token.Revoke(req.Context()); err != nil {
			return err
		}
		return c.Redirect(http.StatusMovedPermanently,
			c.Echo().Reverse(s.RouteName("list_personal")))
	}

	return gctx.Render(http.StatusOK, "oauth2_revoke_confirm.html", gobwebs.Map{
		"token": token,
	})

}

// ListClients ...
func (s *Service) ListClients(c echo.Context) error {
	gctx := c.(*server.Context)
	opts := &database.FilterOptions{
		Filter: sq.Eq{"owner_id": gctx.User.GetID()},
		Filter: sq.And{
			sq.Eq{"owner_id": gctx.User.GetID()},
			sq.Eq{"revoked": false},
		},
	}
	clients, err := GetClients(c.Request().Context(), opts)
	if err != nil {


@@ 171,6 219,77 @@ func (s *Service) AddClient(c echo.Context) error {
	return gctx.Render(http.StatusOK, "oauth2_add_client.html", gmap)
}

// DetailClient ...
func (s *Service) DetailClient(c echo.Context) error {
	id := c.Param("id")
	gctx := c.(*server.Context)
	client, err := GetClientByID(c.Request().Context(), id)
	if err != nil {
		return err
	}
	if client == nil || client.OwnerID != int(gctx.User.GetID()) {
		return echo.NotFoundHandler(c)
	}
	return gctx.Render(http.StatusOK, "oauth2_client_detail.html", gobwebs.Map{
		"client": client,
	})
}

// UnregisterClient ...
func (s *Service) UnregisterClient(c echo.Context) error {
	id := c.Param("id")
	gctx := c.(*server.Context)
	client, err := GetClientByID(c.Request().Context(), id)
	if err != nil {
		return err
	}
	if client == nil || client.OwnerID != int(gctx.User.GetID()) {
		return echo.NotFoundHandler(c)
	}

	err = client.Revoke(c.Request().Context())
	if err != nil {
		return err
	}

	return c.Redirect(http.StatusMovedPermanently,
		c.Echo().Reverse(s.RouteName("list_clients")))
}

// ReissueClient ...
func (s *Service) ReissueClient(c echo.Context) error {
	id := c.Param("id")
	gctx := c.(*server.Context)
	client, err := GetClientByID(c.Request().Context(), id)
	if err != nil {
		return err
	}
	if client == nil || client.OwnerID != int(gctx.User.GetID()) {
		return echo.NotFoundHandler(c)
	}

	err = client.Revoke(c.Request().Context())
	if err != nil {
		return err
	}

	newClient := &Client{
		OwnerID:     int(gctx.User.GetID()),
		Name:        client.Name,
		Description: client.Description,
		RedirectURL: client.RedirectURL,
		ClientURL:   client.ClientURL,
	}
	token := newClient.GenerateKeys()
	if err := newClient.Store(c.Request().Context()); err != nil {
		return err
	}
	return gctx.Render(http.StatusOK, "oauth2_add_client_done.html", gobwebs.Map{
		"client": newClient,
		"token":  token,
	})
}

func oauth2Redirect(c echo.Context, redirectURI string, params gobwebs.Map) error {
	parts, err := url.Parse(redirectURI)
	if err != nil {