GetExternalProviderRedirectURL returns the URL to start the oauth flow with the corresponding oauth provider
(w http.ResponseWriter, r *http.Request, linkingTargetUser *models.User)
| 34 | |
| 35 | // GetExternalProviderRedirectURL returns the URL to start the oauth flow with the corresponding oauth provider |
| 36 | func (a *API) GetExternalProviderRedirectURL(w http.ResponseWriter, r *http.Request, linkingTargetUser *models.User) (string, error) { |
| 37 | ctx := r.Context() |
| 38 | db := a.db.WithContext(ctx) |
| 39 | config := a.config |
| 40 | |
| 41 | query := r.URL.Query() |
| 42 | providerType := query.Get("provider") |
| 43 | scopes := query.Get("scopes") |
| 44 | codeChallenge := query.Get("code_challenge") |
| 45 | codeChallengeMethod := query.Get("code_challenge_method") |
| 46 | |
| 47 | p, pConfig, err := a.Provider(ctx, providerType, scopes) |
| 48 | if err != nil { |
| 49 | return "", apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "Unsupported provider: %+v", err).WithInternalError(err) |
| 50 | } |
| 51 | |
| 52 | inviteToken := query.Get("invite_token") |
| 53 | if inviteToken != "" { |
| 54 | _, userErr := models.FindUserByConfirmationToken(db, inviteToken) |
| 55 | if userErr != nil { |
| 56 | if models.IsNotFoundError(userErr) { |
| 57 | return "", apierrors.NewNotFoundError(apierrors.ErrorCodeUserNotFound, "User identified by token not found") |
| 58 | } |
| 59 | return "", apierrors.NewInternalServerError("Database error finding user").WithInternalError(userErr) |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | redirectURL := utilities.GetReferrer(r, config) |
| 64 | log := observability.GetLogEntry(r).Entry |
| 65 | log.WithField("provider", providerType).Info("Redirecting to external provider") |
| 66 | if err := validatePKCEParams(codeChallengeMethod, codeChallenge); err != nil { |
| 67 | return "", err |
| 68 | } |
| 69 | |
| 70 | authUrlParams := make([]oauth2.AuthCodeOption, 0) |
| 71 | query.Del("scopes") |
| 72 | query.Del("provider") |
| 73 | query.Del("code_challenge") |
| 74 | query.Del("code_challenge_method") |
| 75 | for key := range query { |
| 76 | if key == "workos_provider" { |
| 77 | // See https://workos.com/docs/reference/sso/authorize/get |
| 78 | authUrlParams = append(authUrlParams, oauth2.SetAuthURLParam("provider", query.Get(key))) |
| 79 | } else { |
| 80 | authUrlParams = append(authUrlParams, oauth2.SetAuthURLParam(key, query.Get(key))) |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | // Handle OAuthClientState for providers that require PKCE on their end |
| 85 | var oauthClientStateID *uuid.UUID |
| 86 | if oauthProvider, ok := p.(provider.OAuthProvider); ok && oauthProvider.RequiresPKCE() { |
| 87 | codeVerifier := oauth2.GenerateVerifier() |
| 88 | oauthClientState := models.NewOAuthClientState(providerType, &codeVerifier) |
| 89 | err := db.Create(oauthClientState) |
| 90 | if err != nil { |
| 91 | return "", err |
| 92 | } |
| 93 | oauthClientStateID = &oauthClientState.ID |
no test coverage detected