github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/auth/register.go (about)

     1  package auth
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/cozy/cozy-stack/model/oauth"
    10  	"github.com/cozy/cozy-stack/pkg/config/config"
    11  	"github.com/cozy/cozy-stack/pkg/consts"
    12  	"github.com/cozy/cozy-stack/pkg/couchdb"
    13  	"github.com/cozy/cozy-stack/pkg/limits"
    14  	"github.com/cozy/cozy-stack/web/middlewares"
    15  	"github.com/labstack/echo/v4"
    16  )
    17  
    18  func registerClient(c echo.Context) error {
    19  	instance := middlewares.GetInstance(c)
    20  	err := config.GetRateLimiter().CheckRateLimit(instance, limits.OAuthClientType)
    21  	if limits.IsLimitReachedOrExceeded(err) {
    22  		return echo.NewHTTPError(http.StatusNotFound, "Not found")
    23  	}
    24  	client := new(oauth.Client)
    25  	if err := json.NewDecoder(c.Request().Body).Decode(client); err != nil {
    26  		return err
    27  	}
    28  	// We do not allow the creation of clients allowed to have an empty scope
    29  	// ("login" scope), except via the CLI.
    30  	if client.AllowLoginScope {
    31  		if _, ok := middlewares.GetCLIPermission(c); !ok {
    32  			return echo.NewHTTPError(http.StatusUnauthorized,
    33  				"Not authorized to create client with given parameters")
    34  		}
    35  	}
    36  	if err := client.Create(instance); err != nil {
    37  		return c.JSON(err.Code, err)
    38  	}
    39  	return c.JSON(http.StatusCreated, client)
    40  }
    41  
    42  func readClient(c echo.Context) error {
    43  	client := c.Get("client").(*oauth.Client)
    44  	client.TransformIDAndRev()
    45  	return c.JSON(http.StatusOK, client)
    46  }
    47  
    48  func updateClient(c echo.Context) error {
    49  	instance := middlewares.GetInstance(c)
    50  	err := config.GetRateLimiter().CheckRateLimit(instance, limits.OAuthClientType)
    51  	if limits.IsLimitReachedOrExceeded(err) {
    52  		return echo.NewHTTPError(http.StatusNotFound, "Not found")
    53  	}
    54  
    55  	clientID := c.Param("client-id")
    56  	defer LockOAuthClient(instance, clientID)()
    57  
    58  	oldClient, err := oauth.FindClient(instance, clientID)
    59  	if err != nil {
    60  		return c.JSON(http.StatusNotFound, echo.Map{
    61  			"error": "Client not found",
    62  		})
    63  	}
    64  	if err := checkClientToken(c, oldClient); err != nil {
    65  		return c.JSON(http.StatusUnauthorized, echo.Map{
    66  			"error": err.Error(),
    67  		})
    68  	}
    69  
    70  	client := new(oauth.Client)
    71  	if err := json.NewDecoder(c.Request().Body).Decode(client); err != nil {
    72  		return err
    73  	}
    74  	if err := client.Update(instance, oldClient); err != nil {
    75  		return c.JSON(err.Code, err)
    76  	}
    77  	return c.JSON(http.StatusOK, client)
    78  }
    79  
    80  func deleteClient(c echo.Context) error {
    81  	instance := middlewares.GetInstance(c)
    82  	clientID := c.Param("client-id")
    83  	defer LockOAuthClient(instance, clientID)()
    84  
    85  	client, err := oauth.FindClient(instance, clientID)
    86  	if err != nil {
    87  		if couchdb.IsNotFoundError(err) {
    88  			return c.NoContent(http.StatusNoContent)
    89  		}
    90  		return c.JSON(http.StatusInternalServerError, echo.Map{
    91  			"error": err.Error(),
    92  		})
    93  	}
    94  	if err := checkClientToken(c, client); err != nil {
    95  		return c.JSON(http.StatusUnauthorized, echo.Map{
    96  			"error": err.Error(),
    97  		})
    98  	}
    99  	if err := client.Delete(instance); err != nil {
   100  		return c.JSON(err.Code, err)
   101  	}
   102  	return c.NoContent(http.StatusNoContent)
   103  }
   104  
   105  func checkRegistrationToken(next echo.HandlerFunc) echo.HandlerFunc {
   106  	return func(c echo.Context) error {
   107  		instance := middlewares.GetInstance(c)
   108  		client, err := oauth.FindClient(instance, c.Param("client-id"))
   109  		if err != nil {
   110  			return c.JSON(http.StatusNotFound, echo.Map{
   111  				"error": "Client not found",
   112  			})
   113  		}
   114  		if err := checkClientToken(c, client); err != nil {
   115  			return c.JSON(http.StatusUnauthorized, echo.Map{
   116  				"error": err.Error(),
   117  			})
   118  		}
   119  		c.Set("client", client)
   120  		return next(c)
   121  	}
   122  }
   123  
   124  func checkClientToken(c echo.Context, client *oauth.Client) error {
   125  	header := c.Request().Header.Get(echo.HeaderAuthorization)
   126  	if !strings.HasPrefix(header, "Bearer ") {
   127  		return errors.New("invalid_token")
   128  	}
   129  	token := header[len("Bearer "):]
   130  	instance := middlewares.GetInstance(c)
   131  	_, ok := client.ValidToken(instance, consts.RegistrationTokenAudience, token)
   132  	if !ok {
   133  		return errors.New("invalid_token")
   134  	}
   135  	return nil
   136  }