@@ 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 {
@@ 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 {