server.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package indieauth
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/owncast/owncast/core/data"
  6. "github.com/pkg/errors"
  7. "github.com/teris-io/shortid"
  8. )
  9. // ServerAuthRequest is n inbound request to authenticate against
  10. // this Owncast instance.
  11. type ServerAuthRequest struct {
  12. Timestamp time.Time
  13. ClientID string
  14. RedirectURI string
  15. CodeChallenge string
  16. State string
  17. Me string
  18. Code string
  19. }
  20. // ServerProfile represents basic user-provided data about this Owncast instance.
  21. type ServerProfile struct {
  22. Name string `json:"name"`
  23. URL string `json:"url"`
  24. Photo string `json:"photo"`
  25. }
  26. // ServerProfileResponse is returned when an auth flow requests the final
  27. // confirmation of the IndieAuth flow.
  28. type ServerProfileResponse struct {
  29. Me string `json:"me,omitempty"`
  30. Profile ServerProfile `json:"profile,omitempty"`
  31. // Error keys need to match the OAuth spec.
  32. Error string `json:"error,omitempty"`
  33. ErrorDescription string `json:"error_description,omitempty"`
  34. }
  35. var pendingServerAuthRequests = map[string]ServerAuthRequest{}
  36. const maxPendingRequests = 1000
  37. // StartServerAuth will handle the authentication for the admin user of this
  38. // Owncast server. Initiated via a GET of the auth endpoint.
  39. // https://indieweb.org/authorization-endpoint
  40. func StartServerAuth(clientID, redirectURI, codeChallenge, state, me string) (*ServerAuthRequest, error) {
  41. if len(pendingServerAuthRequests)+1 >= maxPendingRequests {
  42. return nil, errors.New("Please try again later. Too many pending requests.")
  43. }
  44. code := shortid.MustGenerate()
  45. r := ServerAuthRequest{
  46. ClientID: clientID,
  47. RedirectURI: redirectURI,
  48. CodeChallenge: codeChallenge,
  49. State: state,
  50. Me: me,
  51. Code: code,
  52. Timestamp: time.Now(),
  53. }
  54. pendingServerAuthRequests[code] = r
  55. return &r, nil
  56. }
  57. // CompleteServerAuth will verify that the values provided in the final step
  58. // of the IndieAuth flow are correct, and return some basic profile info.
  59. func CompleteServerAuth(code, redirectURI, clientID string, codeVerifier string) (*ServerProfileResponse, error) {
  60. request, pending := pendingServerAuthRequests[code]
  61. if !pending {
  62. return nil, errors.New("no pending authentication request")
  63. }
  64. if request.RedirectURI != redirectURI {
  65. return nil, errors.New("redirect URI does not match")
  66. }
  67. if request.ClientID != clientID {
  68. return nil, errors.New("client ID does not match")
  69. }
  70. codeChallengeFromRequest := createCodeChallenge(codeVerifier)
  71. if request.CodeChallenge != codeChallengeFromRequest {
  72. return nil, errors.New("code verifier is incorrect")
  73. }
  74. response := ServerProfileResponse{
  75. Me: data.GetServerURL(),
  76. Profile: ServerProfile{
  77. Name: data.GetServerName(),
  78. URL: data.GetServerURL(),
  79. Photo: fmt.Sprintf("%s/%s", data.GetServerURL(), data.GetLogoPath()),
  80. },
  81. }
  82. return &response, nil
  83. }