client.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package indieauth
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "strings"
  10. "github.com/owncast/owncast/core/data"
  11. "github.com/pkg/errors"
  12. log "github.com/sirupsen/logrus"
  13. )
  14. var pendingAuthRequests = make(map[string]*Request)
  15. // StartAuthFlow will begin the IndieAuth flow by generating an auth request.
  16. func StartAuthFlow(authHost, userID, accessToken, displayName string) (*url.URL, error) {
  17. serverURL := data.GetServerURL()
  18. if serverURL == "" {
  19. return nil, errors.New("Owncast server URL must be set when using auth")
  20. }
  21. r, err := createAuthRequest(authHost, userID, displayName, accessToken, serverURL)
  22. if err != nil {
  23. return nil, errors.Wrap(err, "unable to generate IndieAuth request")
  24. }
  25. pendingAuthRequests[r.State] = r
  26. return r.Redirect, nil
  27. }
  28. // HandleCallbackCode will handle the callback from the IndieAuth server
  29. // to continue the next step of the auth flow.
  30. func HandleCallbackCode(code, state string) (*Request, *Response, error) {
  31. request, exists := pendingAuthRequests[state]
  32. if !exists {
  33. return nil, nil, errors.New("no auth requests pending")
  34. }
  35. data := url.Values{}
  36. data.Set("grant_type", "authorization_code")
  37. data.Set("code", code)
  38. data.Set("client_id", request.ClientID)
  39. data.Set("redirect_uri", request.Callback.String())
  40. data.Set("code_verifier", request.CodeVerifier)
  41. client := &http.Client{}
  42. r, err := http.NewRequest("POST", request.Endpoint.String(), strings.NewReader(data.Encode())) // URL-encoded payload
  43. if err != nil {
  44. return nil, nil, err
  45. }
  46. r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  47. r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
  48. res, err := client.Do(r)
  49. if err != nil {
  50. return nil, nil, err
  51. }
  52. defer res.Body.Close()
  53. body, err := io.ReadAll(res.Body)
  54. if err != nil {
  55. return nil, nil, err
  56. }
  57. var response Response
  58. if err := json.Unmarshal(body, &response); err != nil {
  59. return nil, nil, errors.Wrap(err, "unable to parse IndieAuth response")
  60. }
  61. if response.Error != "" || response.ErrorDescription != "" {
  62. errorText := makeIndieAuthClientErrorText(response.Error)
  63. log.Debugln("IndieAuth error:", response.Error, response.ErrorDescription)
  64. return nil, nil, fmt.Errorf("IndieAuth error: %s - %s", errorText, response.ErrorDescription)
  65. }
  66. // In case this IndieAuth server does not use OAuth error keys or has internal
  67. // issues resulting in unstructured errors.
  68. if res.StatusCode < 200 || res.StatusCode > 299 {
  69. log.Debugln("IndieAuth error. status code:", res.StatusCode, "body:", string(body))
  70. return nil, nil, errors.New("there was an error authenticating against IndieAuth server")
  71. }
  72. // Trim any trailing slash so we can accurately compare the two "me" values
  73. meResponseVerifier := strings.TrimRight(response.Me, "/")
  74. meRequestVerifier := strings.TrimRight(request.Me.String(), "/")
  75. // What we sent and what we got back must match
  76. if meRequestVerifier != meResponseVerifier {
  77. return nil, nil, errors.New("indieauth response does not match the initial anticipated auth destination")
  78. }
  79. return request, &response, nil
  80. }
  81. // Error value should be from this list:
  82. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  83. func makeIndieAuthClientErrorText(err string) string {
  84. switch err {
  85. case "invalid_request", "invalid_client":
  86. return "The authentication request was invalid. Please report this to the Owncast project."
  87. case "invalid_grant", "unauthorized_client":
  88. return "This authorization request is unauthorized."
  89. case "unsupported_grant_type":
  90. return "The authorization grant type is not supported by the authorization server."
  91. default:
  92. return err
  93. }
  94. }