github_callback.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package user
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strings"
  8. "golang.org/x/oauth2"
  9. "gorm.io/gorm"
  10. "github.com/google/go-github/github"
  11. "github.com/porter-dev/porter/api/server/authn"
  12. "github.com/porter-dev/porter/api/server/handlers"
  13. "github.com/porter-dev/porter/api/server/shared"
  14. "github.com/porter-dev/porter/api/server/shared/apierrors"
  15. "github.com/porter-dev/porter/api/server/shared/config"
  16. "github.com/porter-dev/porter/internal/models"
  17. )
  18. type UserOAuthGithubCallbackHandler struct {
  19. handlers.PorterHandlerReadWriter
  20. }
  21. func NewUserOAuthGithubCallbackHandler(
  22. config *config.Config,
  23. decoderValidator shared.RequestDecoderValidator,
  24. writer shared.ResultWriter,
  25. ) *UserOAuthGithubCallbackHandler {
  26. return &UserOAuthGithubCallbackHandler{
  27. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  28. }
  29. }
  30. func (p *UserOAuthGithubCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  31. session, err := p.Config().Store.Get(r, p.Config().ServerConf.CookieName)
  32. if err != nil {
  33. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  34. return
  35. }
  36. if _, ok := session.Values["state"]; !ok {
  37. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  38. return
  39. }
  40. if r.URL.Query().Get("state") != session.Values["state"] {
  41. p.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  42. return
  43. }
  44. token, err := p.Config().GithubConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
  45. if err != nil {
  46. p.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  47. return
  48. }
  49. if !token.Valid() {
  50. p.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("invalid token")))
  51. return
  52. }
  53. // otherwise, create the user if not exists
  54. user, err := upsertUserFromToken(p.Config(), token)
  55. if err != nil && strings.Contains(err.Error(), "already registered") {
  56. http.Redirect(w, r, "/login?error="+url.QueryEscape(err.Error()), 302)
  57. return
  58. } else if err != nil {
  59. http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  60. return
  61. }
  62. // save the user as authenticated in the session
  63. if err := authn.SaveUserAuthenticated(w, r, p.Config(), user); err != nil {
  64. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  65. return
  66. }
  67. if session.Values["query_params"] != "" {
  68. http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
  69. } else {
  70. http.Redirect(w, r, "/dashboard", 302)
  71. }
  72. }
  73. func upsertUserFromToken(config *config.Config, tok *oauth2.Token) (*models.User, error) {
  74. // determine if the user already exists
  75. client := github.NewClient(config.GithubConf.Client(oauth2.NoContext, tok))
  76. githubUser, _, err := client.Users.Get(context.Background(), "")
  77. if err != nil {
  78. return nil, err
  79. }
  80. user, err := config.Repo.User().ReadUserByGithubUserID(*githubUser.ID)
  81. // if the user does not exist, create new user
  82. if err != nil && err == gorm.ErrRecordNotFound {
  83. emails, _, err := client.Users.ListEmails(context.Background(), &github.ListOptions{})
  84. if err != nil {
  85. return nil, err
  86. }
  87. primary := ""
  88. verified := false
  89. // get the primary email
  90. for _, email := range emails {
  91. if email.GetPrimary() {
  92. primary = email.GetEmail()
  93. verified = email.GetVerified()
  94. break
  95. }
  96. }
  97. if primary == "" {
  98. return nil, fmt.Errorf("github user must have an email")
  99. }
  100. // check if a user with that email address already exists
  101. _, err = config.Repo.User().ReadUserByEmail(primary)
  102. if err == gorm.ErrRecordNotFound {
  103. user = &models.User{
  104. Email: primary,
  105. EmailVerified: !config.Metadata.Email || verified,
  106. GithubUserID: githubUser.GetID(),
  107. }
  108. user, err = config.Repo.User().CreateUser(user)
  109. if err != nil {
  110. return nil, err
  111. }
  112. } else if err == nil {
  113. return nil, fmt.Errorf("email already registered")
  114. } else if err != nil {
  115. return nil, err
  116. }
  117. } else if err != nil {
  118. return nil, fmt.Errorf("unexpected error occurred:%s", err.Error())
  119. }
  120. return user, nil
  121. }