| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- package user
- import (
- "context"
- "fmt"
- "net/http"
- "net/url"
- "strings"
- "golang.org/x/oauth2"
- "gorm.io/gorm"
- "github.com/google/go-github/v41/github"
- "github.com/porter-dev/porter/api/server/authn"
- "github.com/porter-dev/porter/api/server/handlers"
- "github.com/porter-dev/porter/api/server/shared"
- "github.com/porter-dev/porter/api/server/shared/apierrors"
- "github.com/porter-dev/porter/api/server/shared/config"
- "github.com/porter-dev/porter/internal/analytics"
- "github.com/porter-dev/porter/internal/models"
- )
- type UserOAuthGithubCallbackHandler struct {
- handlers.PorterHandlerReadWriter
- }
- func NewUserOAuthGithubCallbackHandler(
- config *config.Config,
- decoderValidator shared.RequestDecoderValidator,
- writer shared.ResultWriter,
- ) *UserOAuthGithubCallbackHandler {
- return &UserOAuthGithubCallbackHandler{
- PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
- }
- }
- func (p *UserOAuthGithubCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- session, err := p.Config().Store.Get(r, p.Config().ServerConf.CookieName)
- if err != nil {
- p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
- return
- }
- if _, ok := session.Values["state"]; !ok {
- p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
- return
- }
- if r.URL.Query().Get("state") != session.Values["state"] {
- p.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
- return
- }
- token, err := p.Config().GithubConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
- if err != nil {
- p.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
- return
- }
- if !token.Valid() {
- p.HandleAPIError(w, r, apierrors.NewErrForbidden(fmt.Errorf("invalid token")))
- return
- }
- // otherwise, create the user if not exists
- user, err := upsertUserFromToken(p.Config(), token)
- if err != nil && strings.Contains(err.Error(), "already registered") {
- http.Redirect(w, r, "/login?error="+url.QueryEscape(err.Error()), 302)
- return
- } else if err != nil {
- http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
- return
- }
- p.Config().AnalyticsClient.Identify(analytics.CreateSegmentIdentifyUser(user))
- // save the user as authenticated in the session
- redirect, err := authn.SaveUserAuthenticated(w, r, p.Config(), user)
- if err != nil {
- p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
- return
- }
- // non-fatal send email verification
- if !user.EmailVerified {
- err = startEmailVerification(p.Config(), w, r, user)
- if err != nil {
- p.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
- }
- }
- if redirect != "" {
- http.Redirect(w, r, redirect, http.StatusFound)
- return
- }
- http.Redirect(w, r, "/dashboard", 302)
- }
- func upsertUserFromToken(config *config.Config, tok *oauth2.Token) (*models.User, error) {
- // determine if the user already exists
- client := github.NewClient(config.GithubConf.Client(oauth2.NoContext, tok))
- githubUser, _, err := client.Users.Get(context.Background(), "")
- if err != nil {
- return nil, err
- }
- user, err := config.Repo.User().ReadUserByGithubUserID(*githubUser.ID)
- // if the user does not exist, create new user
- if err != nil && err == gorm.ErrRecordNotFound {
- emails, _, err := client.Users.ListEmails(context.Background(), &github.ListOptions{})
- if err != nil {
- return nil, err
- }
- primary := ""
- verified := false
- // get the primary email
- for _, email := range emails {
- if email.GetPrimary() {
- primary = email.GetEmail()
- verified = email.GetVerified()
- break
- }
- }
- if primary == "" {
- return nil, fmt.Errorf("github user must have an email")
- }
- if err := checkUserRestrictions(config.ServerConf, primary); err != nil {
- return nil, err
- }
- // check if a user with that email address already exists
- _, err = config.Repo.User().ReadUserByEmail(primary)
- if err == gorm.ErrRecordNotFound {
- user = &models.User{
- Email: primary,
- EmailVerified: !config.Metadata.Email || verified,
- GithubUserID: githubUser.GetID(),
- }
- user, err = config.Repo.User().CreateUser(user)
- if err != nil {
- return nil, err
- }
- err = addUserToDefaultProject(config, user)
- if err != nil {
- return nil, err
- }
- } else if err == nil {
- return nil, fmt.Errorf("email already registered")
- } else if err != nil {
- return nil, err
- }
- } else if err != nil {
- return nil, fmt.Errorf("unexpected error occurred:%s", err.Error())
- }
- return user, nil
- }
|