diff --git a/README.md b/README.md index 5949d8192..cf0980ade 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ The default group to assign all new users to. ### External Authentication Providers -We support `apple`, `azure`, `bitbucket`, `discord`, `facebook`, `github`, `gitlab`, `google`, `linkedin`, `notion`, `spotify`, `slack`, `twitch` and `twitter` for external authentication. +We support `apple`, `azure`, `bitbucket`, `discord`, `facebook`, `github`, `gitlab`, `google`, `linkedin`, `notion`, `spotify`, `slack`, `twitch`, `twitter` and `workos` for external authentication. Use the names as the keys underneath `external` to configure each separately. @@ -514,7 +514,8 @@ Returns the publicly available settings for this gotrue instance. "slack": true, "spotify": true, "twitch": true, - "twitter": true + "twitter": true, + "workos": true, }, "disable_signup": false, "autoconfirm": false @@ -922,7 +923,7 @@ Get access_token from external oauth provider query params: ``` -provider=apple | azure | bitbucket | discord | facebook | github | gitlab | google | linkedin | notion | slack | spotify | twitch | twitter +provider=apple | azure | bitbucket | discord | facebook | github | gitlab | google | linkedin | notion | slack | spotify | twitch | twitter | workos scopes= ``` diff --git a/api/external.go b/api/external.go index 4d4a96c3b..9df6b6e36 100644 --- a/api/external.go +++ b/api/external.go @@ -38,15 +38,16 @@ func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) e ctx := r.Context() config := a.getConfig(ctx) - providerType := r.URL.Query().Get("provider") - scopes := r.URL.Query().Get("scopes") + query := r.URL.Query() + providerType := query.Get("provider") + scopes := query.Get("scopes") - p, err := a.Provider(ctx, providerType, scopes) + p, err := a.Provider(ctx, providerType, scopes, &query) if err != nil { return badRequestError("Unsupported provider: %+v", err).WithInternalError(err) } - inviteToken := r.URL.Query().Get("invite_token") + inviteToken := query.Get("invite_token") if inviteToken != "" { _, userErr := models.FindUserByConfirmationToken(a.db, inviteToken) if userErr != nil { @@ -57,7 +58,7 @@ func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) e } } - redirectURL := a.getRedirectURLOrReferrer(r, r.URL.Query().Get("redirect_to")) + redirectURL := a.getRedirectURLOrReferrer(r, query.Get("redirect_to")) log := getLogEntry(r) log.WithField("provider", providerType).Info("Redirecting to external provider") @@ -383,7 +384,7 @@ func (a *API) loadExternalState(ctx context.Context, state string) (context.Cont } // Provider returns a Provider interface for the given name. -func (a *API) Provider(ctx context.Context, name string, scopes string) (provider.Provider, error) { +func (a *API) Provider(ctx context.Context, name string, scopes string, query *url.Values) (provider.Provider, error) { config := a.getConfig(ctx) name = strings.ToLower(name) @@ -416,6 +417,8 @@ func (a *API) Provider(ctx context.Context, name string, scopes string) (provide return provider.NewTwitchProvider(config.External.Twitch, scopes) case "twitter": return provider.NewTwitterProvider(config.External.Twitter, scopes) + case "workos": + return provider.NewWorkOSProvider(config.External.WorkOS, query) case "saml": return provider.NewSamlProvider(config.External.Saml, a.db, getInstanceID(ctx)) case "zoom": diff --git a/api/external_azure_test.go b/api/external_azure_test.go index b0599c45f..4e8c874bf 100644 --- a/api/external_azure_test.go +++ b/api/external_azure_test.go @@ -67,7 +67,6 @@ func AzureTestSignupSetup(ts *ExternalTestSuite, tokenCount *int, userCount *int func (ts *ExternalTestSuite) TestSignupExternalAzure_AuthorizationCode() { ts.Config.DisableSignup = false - ts.createUser("azuretestid", "azure@example.com", "Azure Test", "", "") tokenCount, userCount := 0, 0 code := "authcode" server := AzureTestSignupSetup(ts, &tokenCount, &userCount, code, azureUser) @@ -106,7 +105,7 @@ func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupErrorWhenNoEmai func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupSuccessWithPrimaryEmail() { ts.Config.DisableSignup = true - ts.createUser("azuretestid", "azure@example.com", "Azure Test", "", "") + ts.createUser("azuretestid", "azure@example.com", "Azure Test", "http://example.com/avatar", "") tokenCount, userCount := 0, 0 code := "authcode" @@ -115,12 +114,12 @@ func (ts *ExternalTestSuite) TestSignupExternalAzureDisableSignupSuccessWithPrim u := performAuthorization(ts, "azure", code, "") - assertAuthorizationSuccess(ts, u, tokenCount, userCount, "azure@example.com", "Azure Test", "azuretestid", "") + assertAuthorizationSuccess(ts, u, tokenCount, userCount, "azure@example.com", "Azure Test", "azuretestid", "http://example.com/avatar") } func (ts *ExternalTestSuite) TestInviteTokenExternalAzureSuccessWhenMatchingToken() { - // name and avatar should be populated from Azure API - ts.createUser("azuretestid", "azure@example.com", "", "", "invite_token") + // name should be populated from Azure API + ts.createUser("azuretestid", "azure@example.com", "", "http://example.com/avatar", "invite_token") tokenCount, userCount := 0, 0 code := "authcode" @@ -129,7 +128,7 @@ func (ts *ExternalTestSuite) TestInviteTokenExternalAzureSuccessWhenMatchingToke u := performAuthorization(ts, "azure", code, "invite_token") - assertAuthorizationSuccess(ts, u, tokenCount, userCount, "azure@example.com", "Azure Test", "azuretestid", "") + assertAuthorizationSuccess(ts, u, tokenCount, userCount, "azure@example.com", "Azure Test", "azuretestid", "http://example.com/avatar") } func (ts *ExternalTestSuite) TestInviteTokenExternalAzureErrorWhenNoMatchingToken() { diff --git a/api/external_oauth.go b/api/external_oauth.go index 3a4899efc..0e7e72a44 100644 --- a/api/external_oauth.go +++ b/api/external_oauth.go @@ -141,7 +141,7 @@ func (a *API) oAuth1Callback(ctx context.Context, r *http.Request, providerType // OAuthProvider returns the corresponding oauth provider as an OAuthProvider interface func (a *API) OAuthProvider(ctx context.Context, name string) (provider.OAuthProvider, error) { - providerCandidate, err := a.Provider(ctx, name, "") + providerCandidate, err := a.Provider(ctx, name, "", nil) if err != nil { return nil, err } diff --git a/api/external_test.go b/api/external_test.go index 9aee2c30c..d6260e24c 100644 --- a/api/external_test.go +++ b/api/external_test.go @@ -120,7 +120,11 @@ func assertAuthorizationSuccess(ts *ExternalTestSuite, u *url.URL, tokenCount in ts.Require().NoError(err) ts.Equal(providerId, user.UserMetaData["provider_id"]) ts.Equal(name, user.UserMetaData["full_name"]) - ts.Equal(avatar, user.UserMetaData["avatar_url"]) + if avatar == "" { + ts.Equal(nil, user.UserMetaData["avatar_url"]) + } else { + ts.Equal(avatar, user.UserMetaData["avatar_url"]) + } } func assertAuthorizationFailure(ts *ExternalTestSuite, u *url.URL, errorDescription string, errorType string, email string) { diff --git a/api/external_workos_test.go b/api/external_workos_test.go new file mode 100644 index 000000000..b2e1e599e --- /dev/null +++ b/api/external_workos_test.go @@ -0,0 +1,221 @@ +package api + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + + jwt "github.com/golang-jwt/jwt" +) + +const ( + workosUser string = `{"id":"test_prof_workos","first_name":"John","last_name":"Doe","email":"workos@example.com","connection_id":"test_conn_1","organization_id":"test_org_1","connection_type":"test","idp_id":"test_idp_1","object": "profile","raw_attributes": {}}` + workosUserWrongEmail string = `{"id":"test_prof_workos","first_name":"John","last_name":"Doe","email":"other@example.com","connection_id":"test_conn_1","organization_id":"test_org_1","connection_type":"test","idp_id":"test_idp_1","object": "profile","raw_attributes": {}}` + workosUserNoEmail string = `{"id":"test_prof_workos","first_name":"John","last_name":"Doe","connection_id":"test_conn_1","organization_id":"test_org_1","connection_type":"test","idp_id":"test_idp_1","object": "profile","raw_attributes": {}}` +) + +func (ts *ExternalTestSuite) TestSignupExternalWorkOSWithConnection() { + connection := "test_connection_id" + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost/authorize?provider=workos&connection=%s", connection), nil) + w := httptest.NewRecorder() + ts.API.handler.ServeHTTP(w, req) + ts.Require().Equal(http.StatusFound, w.Code) + u, err := url.Parse(w.Header().Get("Location")) + ts.Require().NoError(err, "redirect url parse failed") + q := u.Query() + ts.Equal(ts.Config.External.WorkOS.RedirectURI, q.Get("redirect_uri")) + ts.Equal(ts.Config.External.WorkOS.ClientID, q.Get("client_id")) + ts.Equal("code", q.Get("response_type")) + ts.Equal("", q.Get("scope")) + ts.Equal(connection, q.Get("connection")) + + claims := ExternalProviderClaims{} + p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}} + _, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) { + return []byte(ts.Config.JWT.Secret), nil + }) + ts.Require().NoError(err) + + ts.Equal("workos", claims.Provider) + ts.Equal(ts.Config.SiteURL, claims.SiteURL) +} + +func (ts *ExternalTestSuite) TestSignupExternalWorkOSWithOrganization() { + organization := "test_organization_id" + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost/authorize?provider=workos&organization=%s", organization), nil) + w := httptest.NewRecorder() + ts.API.handler.ServeHTTP(w, req) + ts.Require().Equal(http.StatusFound, w.Code) + u, err := url.Parse(w.Header().Get("Location")) + ts.Require().NoError(err, "redirect url parse failed") + q := u.Query() + ts.Equal(ts.Config.External.WorkOS.RedirectURI, q.Get("redirect_uri")) + ts.Equal(ts.Config.External.WorkOS.ClientID, q.Get("client_id")) + ts.Equal("code", q.Get("response_type")) + ts.Equal("", q.Get("scope")) + ts.Equal(organization, q.Get("organization")) + + claims := ExternalProviderClaims{} + p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}} + _, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) { + return []byte(ts.Config.JWT.Secret), nil + }) + ts.Require().NoError(err) + + ts.Equal("workos", claims.Provider) + ts.Equal(ts.Config.SiteURL, claims.SiteURL) +} + +func (ts *ExternalTestSuite) TestSignupExternalWorkOSWithProvider() { + provider := "test_provider" + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost/authorize?provider=workos&workos_provider=%s", provider), nil) + w := httptest.NewRecorder() + ts.API.handler.ServeHTTP(w, req) + ts.Require().Equal(http.StatusFound, w.Code) + u, err := url.Parse(w.Header().Get("Location")) + ts.Require().NoError(err, "redirect url parse failed") + q := u.Query() + ts.Equal(ts.Config.External.WorkOS.RedirectURI, q.Get("redirect_uri")) + ts.Equal(ts.Config.External.WorkOS.ClientID, q.Get("client_id")) + ts.Equal("code", q.Get("response_type")) + ts.Equal("", q.Get("scope")) + ts.Equal(provider, q.Get("provider")) + + claims := ExternalProviderClaims{} + p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}} + _, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) { + return []byte(ts.Config.JWT.Secret), nil + }) + ts.Require().NoError(err) + + ts.Equal("workos", claims.Provider) + ts.Equal(ts.Config.SiteURL, claims.SiteURL) +} + +func WorkosTestSignupSetup(ts *ExternalTestSuite, tokenCount *int, userCount *int, code string, user string) *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/sso/token": + // WorkOS returns the user data along with the token. + *tokenCount++ + *userCount++ + ts.Equal(code, r.FormValue("code")) + ts.Equal("authorization_code", r.FormValue("grant_type")) + ts.Equal(ts.Config.External.WorkOS.RedirectURI, r.FormValue("redirect_uri")) + + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, `{"access_token":"workos_token","expires_in":100000,"profile":%s}`, user) + default: + fmt.Printf("%s", r.URL.Path) + w.WriteHeader(500) + ts.Fail("unknown workos oauth call %s", r.URL.Path) + } + })) + + ts.Config.External.WorkOS.URL = server.URL + + return server +} + +func (ts *ExternalTestSuite) TestSignupExternalWorkosAuthorizationCode() { + ts.Config.DisableSignup = false + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUser) + defer server.Close() + + u := performAuthorization(ts, "workos", code, "") + + assertAuthorizationSuccess(ts, u, tokenCount, userCount, "workos@example.com", "John Doe", "test_prof_workos", "") +} + +func (ts *ExternalTestSuite) TestSignupExternalWorkosDisableSignupErrorWhenNoUser() { + ts.Config.DisableSignup = true + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUser) + defer server.Close() + + u := performAuthorization(ts, "workos", code, "") + + assertAuthorizationFailure(ts, u, "Signups not allowed for this instance", "access_denied", "workos@example.com") +} + +func (ts *ExternalTestSuite) TestSignupExternalWorkosDisableSignupErrorWhenEmptyEmail() { + ts.Config.DisableSignup = true + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUserNoEmail) + defer server.Close() + + u := performAuthorization(ts, "workos", code, "") + + assertAuthorizationFailure(ts, u, "Error getting user email from external provider", "server_error", "workos@example.com") +} + +func (ts *ExternalTestSuite) TestSignupExternalWorkosDisableSignupSuccessWithPrimaryEmail() { + ts.Config.DisableSignup = true + + ts.createUser("test_prof_workos", "workos@example.com", "John Doe", "http://example.com/avatar", "") + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUser) + defer server.Close() + + u := performAuthorization(ts, "workos", code, "") + + assertAuthorizationSuccess(ts, u, tokenCount, userCount, "workos@example.com", "John Doe", "test_prof_workos", "http://example.com/avatar") +} + +func (ts *ExternalTestSuite) TestInviteTokenExternalWorkosSuccessWhenMatchingToken() { + ts.createUser("test_prof_workos", "workos@example.com", "", "http://example.com/avatar", "invite_token") + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUser) + defer server.Close() + + u := performAuthorization(ts, "workos", code, "invite_token") + + assertAuthorizationSuccess(ts, u, tokenCount, userCount, "workos@example.com", "John Doe", "test_prof_workos", "http://example.com/avatar") +} + +func (ts *ExternalTestSuite) TestInviteTokenExternalWorkosErrorWhenNoMatchingToken() { + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUser) + defer server.Close() + + w := performAuthorizationRequest(ts, "workos", "invite_token") + ts.Require().Equal(http.StatusNotFound, w.Code) +} + +func (ts *ExternalTestSuite) TestInviteTokenExternalWorkosErrorWhenWrongToken() { + ts.createUser("test_prof_workos", "workos@example.com", "", "", "invite_token") + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUser) + defer server.Close() + + w := performAuthorizationRequest(ts, "workos", "wrong_token") + ts.Require().Equal(http.StatusNotFound, w.Code) +} + +func (ts *ExternalTestSuite) TestInviteTokenExternalWorkosErrorWhenEmailDoesntMatch() { + ts.createUser("test_prof_workos", "workos@example.com", "", "", "invite_token") + + tokenCount, userCount := 0, 0 + code := "authcode" + server := WorkosTestSignupSetup(ts, &tokenCount, &userCount, code, workosUserWrongEmail) + defer server.Close() + + u := performAuthorization(ts, "workos", code, "invite_token") + + assertAuthorizationFailure(ts, u, "Invited email does not match emails from external provider", "invalid_request", "") +} diff --git a/api/provider/workos.go b/api/provider/workos.go new file mode 100644 index 000000000..91ff8dd33 --- /dev/null +++ b/api/provider/workos.go @@ -0,0 +1,126 @@ +package provider + +import ( + "context" + "errors" + "net/url" + "strings" + + "github.com/mitchellh/mapstructure" + "github.com/netlify/gotrue/conf" + "golang.org/x/oauth2" +) + +const ( + defaultWorkOSAPIBase = "api.workos.com" +) + +type workosProvider struct { + *oauth2.Config + APIPath string + AuthCodeOptions []oauth2.AuthCodeOption +} + +// See https://workos.com/docs/reference/sso/profile. +type workosUser struct { + ID string `mapstructure:"id"` + ConnectionID string `mapstructure:"connection_id"` + OrganizationID string `mapstructure:"organization_id"` + ConnectionType string `mapstructure:"connection_type"` + Email string `mapstructure:"email"` + FirstName string `mapstructure:"first_name"` + LastName string `mapstructure:"last_name"` + Object string `mapstructure:"object"` + IdpID string `mapstructure:"idp_id"` + RawAttributes map[string]interface{} `mapstructure:"raw_attributes"` +} + +// NewWorkOSProvider creates a WorkOS account provider. +func NewWorkOSProvider(ext conf.OAuthProviderConfiguration, query *url.Values) (OAuthProvider, error) { + if err := ext.Validate(); err != nil { + return nil, err + } + apiPath := chooseHost(ext.URL, defaultWorkOSAPIBase) + + // Attach custom query parameters to the WorkOS authorization URL. + // See https://workos.com/docs/reference/sso/authorize/get. + var authCodeOptions []oauth2.AuthCodeOption + if query != nil { + if connection := query.Get("connection"); connection != "" { + authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("connection", connection)) + } else if organization := query.Get("organization"); organization != "" { + authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("organization", organization)) + } else if provider := query.Get("workos_provider"); provider != "" { + authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("provider", provider)) + } + + if login_hint := query.Get("login_hint"); login_hint != "" { + authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("login_hint", login_hint)) + } + } + + return &workosProvider{ + Config: &oauth2.Config{ + ClientID: ext.ClientID, + ClientSecret: ext.Secret, + Endpoint: oauth2.Endpoint{ + AuthURL: apiPath + "/sso/authorize", + TokenURL: apiPath + "/sso/token", + }, + RedirectURL: ext.RedirectURI, + }, + APIPath: apiPath, + AuthCodeOptions: authCodeOptions, + }, nil +} + +func (g workosProvider) AuthCodeURL(state string, args ...oauth2.AuthCodeOption) string { + opts := append(args, g.AuthCodeOptions...) + return g.Config.AuthCodeURL(state, opts...) +} + +func (g workosProvider) GetOAuthToken(code string) (*oauth2.Token, error) { + return g.Exchange(oauth2.NoContext, code) +} + +func (g workosProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) { + if tok.AccessToken == "" { + return &UserProvidedData{}, nil + } + + // WorkOS API returns the user's profile data along with the OAuth2 token, so + // we can just convert from `map[string]interface{}` to `workosUser` without + // an additional network request. + var u workosUser + err := mapstructure.Decode(tok.Extra("profile"), &u) + if err != nil { + return nil, err + } + + if u.Email == "" { + return nil, errors.New("Unable to find email with WorkOS provider") + } + + return &UserProvidedData{ + Metadata: &Claims{ + Issuer: g.APIPath, + Subject: u.ID, + Name: strings.TrimSpace(u.FirstName + " " + u.LastName), + Email: u.Email, + EmailVerified: true, + CustomClaims: map[string]interface{}{ + "connection_id": u.ConnectionID, + "organization_id": u.OrganizationID, + }, + + // To be deprecated + FullName: strings.TrimSpace(u.FirstName + " " + u.LastName), + ProviderId: u.ID, + }, + Emails: []Email{{ + Email: u.Email, + Verified: true, + Primary: true, + }}, + }, nil +} diff --git a/api/settings.go b/api/settings.go index 56e760efb..929593ed4 100644 --- a/api/settings.go +++ b/api/settings.go @@ -15,6 +15,7 @@ type ProviderSettings struct { Notion bool `json:"notion"` Spotify bool `json:"spotify"` Slack bool `json:"slack"` + WorkOS bool `json:"workos"` Twitch bool `json:"twitch"` Twitter bool `json:"twitter"` Email bool `json:"email"` @@ -55,6 +56,7 @@ func (a *API) Settings(w http.ResponseWriter, r *http.Request) error { Slack: config.External.Slack.Enabled, Twitch: config.External.Twitch.Enabled, Twitter: config.External.Twitter.Enabled, + WorkOS: config.External.WorkOS.Enabled, Email: config.External.Email.Enabled, Phone: config.External.Phone.Enabled, SAML: config.External.Saml.Enabled, diff --git a/api/settings_test.go b/api/settings_test.go index 922b5ff01..4e02e5bf8 100644 --- a/api/settings_test.go +++ b/api/settings_test.go @@ -41,6 +41,7 @@ func TestSettings_DefaultProviders(t *testing.T) { require.True(t, p.GitLab) require.True(t, p.SAML) require.True(t, p.Twitch) + require.True(t, p.WorkOS) require.True(t, p.Zoom) } diff --git a/conf/configuration.go b/conf/configuration.go index ee9bc0a5e..a224d89a7 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -95,6 +95,7 @@ type ProviderConfiguration struct { Slack OAuthProviderConfiguration `json:"slack"` Twitter OAuthProviderConfiguration `json:"twitter"` Twitch OAuthProviderConfiguration `json:"twitch"` + WorkOS OAuthProviderConfiguration `json:"workos"` Email EmailProviderConfiguration `json:"email"` Phone PhoneProviderConfiguration `json:"phone"` Saml SamlProviderConfiguration `json:"saml"` diff --git a/example.env b/example.env index 44cc6899a..0782f6ec7 100644 --- a/example.env +++ b/example.env @@ -135,13 +135,19 @@ GOTRUE_EXTERNAL_LINKEDIN_SECRET="" GOTRUE_EXTERNAL_SLACK_ENABLED="false" GOTRUE_EXTERNAL_SLACK_CLIENT_ID="" GOTRUE_EXTERNAL_SLACK_SECRET="" -GOTRUE_EXTERNAL_SLACK_REDIRECT_URI="https://localhost:9999/callback" +GOTRUE_EXTERNAL_SLACK_REDIRECT_URI="http://localhost:9999/callback" + +# WorkOS OAuth config +GOTRUE_EXTERNAL_WORKOS_ENABLED="true" +GOTRUE_EXTERNAL_WORKOS_CLIENT_ID="" +GOTRUE_EXTERNAL_WORKOS_SECRET="" +GOTRUE_EXTERNAL_WORKOS_REDIRECT_URI="http://localhost:9999/callback" # Zoom OAuth config GOTRUE_EXTERNAL_ZOOM_ENABLED="false" GOTRUE_EXTERNAL_ZOOM_CLIENT_ID="" GOTRUE_EXTERNAL_ZOOM_SECRET="" -GOTRUE_EXTERNAL_ZOOM_REDIRECT_URI="https://localhost:9999/callback" +GOTRUE_EXTERNAL_ZOOM_REDIRECT_URI="http://localhost:9999/callback" # Phone provider config GOTRUE_SMS_AUTOCONFIRM="false" @@ -191,4 +197,4 @@ GOTRUE_WEBHOOK_EVENTS=validate,signup,login # Cookie config GOTRUE_COOKIE_KEY: "sb" -GOTRUE_COOKIE_DOMAIN: "localhost" \ No newline at end of file +GOTRUE_COOKIE_DOMAIN: "localhost" diff --git a/go.mod b/go.mod index 1563512c6..e8130727e 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/lestrrat-go/jwx v0.9.0 github.com/lib/pq v1.9.0 // indirect github.com/microcosm-cc/bluemonday v1.0.16 // indirect + github.com/mitchellh/mapstructure v1.1.2 github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 github.com/netlify/mailme v1.1.1 github.com/opentracing/opentracing-go v1.1.0 diff --git a/go.sum b/go.sum index 6ca10922b..2fd375e26 100644 --- a/go.sum +++ b/go.sum @@ -420,6 +420,7 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= diff --git a/hack/test.env b/hack/test.env index 9b5d9b89c..6d7f74ffb 100644 --- a/hack/test.env +++ b/hack/test.env @@ -61,6 +61,10 @@ GOTRUE_EXTERNAL_SLACK_ENABLED=true GOTRUE_EXTERNAL_SLACK_CLIENT_ID=testclientid GOTRUE_EXTERNAL_SLACK_SECRET=testsecret GOTRUE_EXTERNAL_SLACK_REDIRECT_URI=https://identity.services.netlify.com/callback +GOTRUE_EXTERNAL_WORKOS_ENABLED=true +GOTRUE_EXTERNAL_WORKOS_CLIENT_ID=testclientid +GOTRUE_EXTERNAL_WORKOS_SECRET=testsecret +GOTRUE_EXTERNAL_WORKOS_REDIRECT_URI=https://identity.services.netlify.com/callback GOTRUE_EXTERNAL_TWITCH_ENABLED=true GOTRUE_EXTERNAL_TWITCH_CLIENT_ID=testclientid GOTRUE_EXTERNAL_TWITCH_SECRET=testsecret