1package server
2
3import (
4 "time"
5
6 "github.com/Azure/go-autorest/autorest/to"
7 "github.com/haileyok/cocoon/internal/helpers"
8 "github.com/haileyok/cocoon/oauth"
9 "github.com/haileyok/cocoon/oauth/constants"
10 "github.com/haileyok/cocoon/oauth/provider"
11 "github.com/labstack/echo/v4"
12)
13
14type OauthParResponse struct {
15 ExpiresIn int64 `json:"expires_in"`
16 RequestURI string `json:"request_uri"`
17}
18
19func (s *Server) handleOauthPar(e echo.Context) error {
20 var parRequest provider.ParRequest
21 if err := e.Bind(&parRequest); err != nil {
22 s.logger.Error("error binding for par request", "error", err)
23 return helpers.ServerError(e, nil)
24 }
25
26 if err := e.Validate(parRequest); err != nil {
27 s.logger.Error("missing parameters for par request", "error", err)
28 return helpers.InputError(e, nil)
29 }
30
31 // TODO: this seems wrong. should be a way to get the entire request url i believe, but this will work for now
32 dpopProof, err := s.oauthProvider.DpopManager.CheckProof(e.Request().Method, "https://"+s.config.Hostname+e.Request().URL.String(), e.Request().Header, nil)
33 if err != nil {
34 s.logger.Error("error getting dpop proof", "error", err)
35 return helpers.InputError(e, to.StringPtr(err.Error()))
36 }
37
38 client, clientAuth, err := s.oauthProvider.AuthenticateClient(e.Request().Context(), parRequest.AuthenticateClientRequestBase, dpopProof, &provider.AuthenticateClientOptions{
39 // rfc9449
40 // https://github.com/bluesky-social/atproto/blob/main/packages/oauth/oauth-provider/src/oauth-provider.ts#L473
41 AllowMissingDpopProof: true,
42 })
43 if err != nil {
44 s.logger.Error("error authenticating client", "client_id", parRequest.ClientID, "error", err)
45 return helpers.InputError(e, to.StringPtr(err.Error()))
46 }
47
48 if parRequest.DpopJkt == nil {
49 if client.Metadata.DpopBoundAccessTokens {
50 parRequest.DpopJkt = to.StringPtr(dpopProof.JKT)
51 }
52 } else {
53 if !client.Metadata.DpopBoundAccessTokens {
54 msg := "dpop bound access tokens are not enabled for this client"
55 s.logger.Error(msg)
56 return helpers.InputError(e, &msg)
57 }
58
59 if dpopProof.JKT != *parRequest.DpopJkt {
60 msg := "supplied dpop jkt does not match header dpop jkt"
61 s.logger.Error(msg)
62 return helpers.InputError(e, &msg)
63 }
64 }
65
66 eat := time.Now().Add(constants.ParExpiresIn)
67 id := oauth.GenerateRequestId()
68
69 authRequest := &provider.OauthAuthorizationRequest{
70 RequestId: id,
71 ClientId: client.Metadata.ClientID,
72 ClientAuth: *clientAuth,
73 Parameters: parRequest,
74 ExpiresAt: eat,
75 }
76
77 if err := s.db.Create(authRequest, nil).Error; err != nil {
78 s.logger.Error("error creating auth request in db", "error", err)
79 return helpers.ServerError(e, nil)
80 }
81
82 uri := oauth.EncodeRequestUri(id)
83
84 return e.JSON(201, OauthParResponse{
85 ExpiresIn: int64(constants.ParExpiresIn.Seconds()),
86 RequestURI: uri,
87 })
88}